Java Persistence/Mapping
Mapping
[edit | edit source]The first thing that you need to do to persist something in Java is define how it is to be persisted. This is called the mapping process (details). There have been many different solutions to the mapping process over the years, including some object databases that didn't require you map anything but let you persist anything directly. Object-relational mapping tools that would generate an object model for a data model that included the mapping and persistence logic in it. ORM products that provided mapping tools to allow the mapping of an existing object model to an existing data model and stored this mapping meta-data in flat files, database tables, XML and finally annotations.
In JPA, mappings can either be stored through Java annotations, or in XML files. One significant aspect of JPA is that only the minimal amount of mapping is required. JPA implementations are required to provide defaults for almost all aspects of mapping an object.
The minimum requirement to mapping an object in JPA is to define which objects can be persisted. This is done through either marking the class with the @Entity
annotation, or adding an <entity>
tag for the class in the persistence unit's ORM XML file. Also the primary key, or unique identifier attribute(s) must be defined for the class. This is done through marking one of the class' fields or properties (get method) with the @Id
annotation, or adding an <id>
tag for the class' attribute in the ORM XML file.
The JPA implementation will default all other mapping information, including defaulting the table name, column names for all defined fields or properties, cardinality and mapping of relationships, all SQL and persistence logic for accessing the objects. Most JPA implementations also provide the option of generating the database tables at runtime, so very little work is required by the developer to rapidly develop a persistent JPA application.
Example object model
[edit | edit source]Example data model
[edit | edit source]Example of a persistent entity mapping in annotations
[edit | edit source]import javax.persistence.*;
...
@Entity
public class Employee {
@Id
private long id;
private String firstName;
private String lastName;
private Address address;
private List<Phone> phones;
private Employee manager;
private List<Employee> managedEmployees;
...
...
}
Example of a persistent entity mapping in XML
[edit | edit source]<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd">
<description>The minimal mappings for a persistent entity in XML.</description>
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id"/>
</attributes>
</entity>
</entity-mappings>
Access Type
[edit | edit source]JPA allows annotations to be placed on either the class field, or on the get
method for the property. This defines the access type of the class, which is either FIELD
or PROPERTY
. Either all annotations must be on the fields, or all annotations on the get
methods, but not both (unless the @Access
annotation is used). JPA does not define a default access type (oddly enough), so the default may depend on the JPA provider. The default is assumed to occur based on where the @Id
annotation is placed, if placed on a field, then the class is using FIELD
access, if placed on a get
method, then the class is using PROPERTY
access. The access type can also be defined through XML on the <entity>
element. The access type can be configured using the @Access
annotation or access-type
XML attribute.
For FIELD
access the class field value will be accessed directly to store and load the value from the database. This is normally done either through reflection, or through generated byte code, but depends on the JPA provider and configuration. The field can be private
or any other access type. FIELD
is normally safer, as it avoids any unwanted side-effect code that may occur in the application get
/set
methods.
For PROPERTY
access the class get
and set
methods will be used to store and load the value from the database. This is normally done either through reflection, or through generated byte code, but depends on the JPA provider and configuration. PROPERTY
has the advantage of allowing the application to perform conversion of the database value when storing it in the object. The user should be careful to not put any side-effects in the get
/set
methods that could interfere with persistence.
JPA 2.0 allows the access type to also be set on a specific field or get
method. This allows for the class to use one default access mechanism, but for one attribute to use a different access type. This can be used for attributes that need to be converted through a set of database specific get
, set
, or allow a specific attribute to avoid side-affects in its get
, set
methods. This is done through specifying the @Access
annotation or XML attribute on the mapped attribute. You may also need to mark the field/property as @Transient
if it has a different attribute name than the mapped attribute.
JPA allows for a persistence unit default <access-type>
element to be set in persistence-unit-defaults
or entity default in entity-mappings
.
- TopLink / EclipseLink : Default to using
FIELD
access. If weaving is enabled, and field access is used the fields are accessed directly using generated byte-code. If not using weaving, or using property access, reflection is used. EclipseLink also supports a third access typeVIRTUAL
, which can be used from the ORM XML to map dynamic properties stored in a properties Map.
Access type example
[edit | edit source]@Entity
@Access(AccessType.FIELD)
public class Employee {
@Id
private long id;
private String firstName;
private String lastName;
@Transient
private Money salary;
@ManyToOne(fetch=FetchType.LAZY)
private Employee manager;
@Access(AccessType.PROPERTY)
private BigDecimal getBDSalary() {
return this.salary.toNumber();
}
private void setBDSalary(BigDecimal salary) {
this.salary = new Money(salary);
}
private Money getSalary() {
return this.salary;
}
private void setSalary(Money salary) {
this.salary = salary;
}
...
}
Common Problems
[edit | edit source]My annotations are ignored
[edit | edit source]- This typically occurs when you annotate both the fields and methods (properties) of the class. You must choose either field or property access, and be consistent. Also when annotating properties you must put the annotation on the get method, not the set method. Also ensure that you have not defined the same mappings in XML, which may be overriding the annotations. You may also have a classpath issue, such as having an old version of the class on the classpath.
Odd behavior
[edit | edit source]- There are many reasons that odd behavior can occur with persistence. One common issue that can cause odd behavior is using property access and putting side effects in your get or set methods. For this reason it is generally recommended to use field access in mapping, i.e. putting your annotations on your variables not your get methods.
- For example consider:
public void setPhones(List<Phone> phones) {
for (Phone phone : phones) {
phone.setOwner(this);
}
this.phones = phones;
}
- This may look innocent, but these side effects can have unexpected consequences. For example if the relationship was
lazy
this would have the effect of always instantiating the collection when set from the database. It could also have consequences with certain JPA implementations for persisting, merging and other operations, causing duplicate inserts, missed updates, or a corrupt object model.
- I have also seen simply incorrect property methods, such as a get method that always returns a new object, or a copy, or set methods that don't actually set the value.
- In general if you are going to use property access, ensure your property methods are free of side effects. Perhaps even use different property methods than your application uses.