Java Persistence/Mapping

From Wikibooks, open books for an open world
Jump to: navigation, search

Mapping[edit]

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]

Employee-model.PNG

Example data model[edit]

Employee-schema.PNG

Example of a persistent entity mapping in annotations[edit]

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]

<?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]

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 @AccessType 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 @AccessType 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-affect 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-affects 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 @AccessType 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 type VIRTUAL, which can be used from the ORM XML to map dynamic properties stored in a properties Map.

Access type example[edit]

@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]

My annotations are ignored[edit]

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]

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.