Home > Articles > Web Development

  • Print
  • + Share This
This chapter is from the book

ORM Features Supported

We have shown you how a programmer would use Entities to perform the runtime persistence operations. We will now show you how to map Entities to a database using OpenJPA. OpenJPA allows mapping Entities via Java Annotations and XML. The choice is up to the developer and usually depends on whether you want to expose the underlying relational database details into the code; or in some cases, whether you have "legacy" Java objects that are not easy to change.

Objects

Throughout this book, we have been showing meet-in-the-middle Mapping. OpenJPA supports top-down, bottom-up, and meet-in-the-middle. It is worth mentioning that the JPA spec defines a common standard for top-down generation of database schemas. Listing 8.26 shows the minimum annotation needed to make a POJO a JPA Entity. Simply by marking a class with the @Entity annotation, you have a persistent capable class. If your database schema follows the JPA naming convention, or if you want to have the database schema generated, then the class can be used. In this case, OpenJPA will look for a table named Customer. The table will have two columns: id and name. The table names and column names will match whether they contain upper- or lowercase spellings.

Listing 8.26. Creating an Entity with Java Annotations

@Entity
public class Customer
{
    private String name;
    private int id;

    public getName(){return name;}
    public setName(String name){this.name=name;}

    public getId(){return id;}
    public setId(int id){this.id = id;}
}

As mentioned earlier, you can also use XML as an alternative to Java Annotations. Listing 8.27 shows an XML entity mapping for the same Customer. As noted previously, some developers prefer to externalize their database mappings to keep the Java code "pure." Some other developers prefer to use Annotations together with XML. In some cases XML and Annotations can be used. In this case, XML will serve as an override for Annotations. This might not make sense when an entity is always mapped to a relational table; however, in other cases XML overrides can provide benefits (for example, enabling you to change a schema name or optimize a query without changing the Java code).

Listing 8.27. Creating an Entity with XML

<entity-mappings
    xmlns="java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
       "java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
    version="1.0"
>
    <entity class=" Customer" />
</entity-mappings>

OpenJPA does a lot of defaulting for you; as long as you match the naming conventions, things will fall into place. This includes naming conventions for relationships and other complex types. For the rest of the section, though, we will show explicit mappings to illustrate the important concepts. In addition, it is often the case that the database schema will not always match the Object model, and will require a meet-in-the-middle mapping—as occurs in our example throughout the book.

Mapping Entities to tables with different names is easy. Listing 8.28 shows the Order Entity mapped to a table called ORDERS. You use a simple annotation called @Table.

Listing 8.28. Mapping an Entity to a Table

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {

Similarly, you can map Entities to a table using XML, as shown in Listing 8.29.

Listing 8.29. Mapping an Entity to a Table with XML

<entity-mappings xmlns="java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
        "java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
    version="1.0"
>

<entity class=" Order ">
    <table name="ORDERS"/>
...
</entity>

The Table annotation and XML both let you specify a schema name as well.

Inheritance

OpenJPA fully supports inheritance in persistent classes. It allows persistent classes to inherit from nonpersistent classes, persistent classes to inherit from other persistent classes, and nonpersistent classes to inherit from persistent classes. It is even possible to form inheritance hierarchies in which persistence skips generations. There are, however, a few important limitations:

  • Persistent classes cannot inherit from certain natively implemented system classes such as java.net.Socket and java.lang.Thread.
  • If a persistent class inherits from a nonpersistent class, the fields of the nonpersistent superclass cannot be persisted.
  • All classes in an inheritance tree must use the same identity type. Identifiers will be covered in the next section.

OpenJPA supports three strategies for Inheritance:

  1. Single Table
  2. Joined
  3. Table Per Class

We described each of these in detail in Chapter 3, "Designing Persistent Object Services."

Single Table Strategy

All JPA providers have to provide an implementation of the Single Table and the Joined strategy. Table Per Class is optional. Single Table mandates all classes in the inheritance hierarchy map into one table. In our book example for OpenJPA, this is our default implementation.

The AbstractCustomer superclass contains a type discriminator. This discriminator will be used to create the correct type at runtime. All the fields are mapped into a single table. Listing 8.30 shows how this mapping would look in OpenJPA.

Listing 8.30. Superclass Mapping with a Single Table

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    protected int customerId;
    ....

The subclasses would then specify a discriminator value. Listing 8.31 shows the mapping for ResidentialCustomer (each subclass would have a similar mapping). Notice that you do not have to specify a table mapping because the superclass handles the mapping.

Listing 8.31. Subclass Mapping with a Single Table

@Entity
@DiscriminatorValue("RESIDENTAL")
public class ResidentialCustomer
extends AbstractCustomer
implements Serializable {
    protected short householdSize;
    protected boolean frequentCustomer;

In Listing 8.32, the same mapping is shown in XML. We show it here just to highlight that mappings can be done in XML.

Listing 8.32. XML Mapping for Single Table

<entity class=" org.pwte.example.domain.AbstractCustomer ">
    <table name="CUSTOMER" />
    <inheritance strategy="SINGLE_TABLE"/>
        ...
</entity>
<entity class=" org.pwte.example.domain.ResidentialCustomer ">
    ...
</entity>

Single table inheritance mapping is the most performant of all inheritance models because it does not require a join to retrieve the persistent data necessary to populate the class hierarchy of a single instance from the database (it still may require a join to retrieve related objects, of course). Similarly, persisting or updating a single persistent instance can often be accomplished with a single INSERT or UPDATE statement. Finally, relations to any other class within a single table inheritance hierarchy are just as efficient as relations to a base class.

However, the larger the inheritance model gets, the "wider" the mapped table gets—in that for every field in the entire inheritance hierarchy, a column must exist in the mapped table. This may have undesirable consequences on the database size, because a wide or deep inheritance hierarchy will result in tables with many mostly empty columns. In addition, changes to any class in the hierarchy would result in changes to the table. This issue is significant because after systems are deployed, it is generally very difficult to change database schemas of existing tables.

Joined Strategy

The Joined Strategy is really the table per class strategy we discussed in Chapter 3. Here, every class in the inheritance chain gets its own table. Each subclass's primary key would also be a foreign key to the primary key table.

Listing 8.33 shows the AbstractCustomer with the Inheritance strategy of Joined. Notice it is mapped to an ABSTRACT_CUSTOMER table.

Listing 8.33. Mapping Superclass with the Joined Strategy

@Entity
@Inheritance(strategy=JOINED)
@Table(name = "ABSTRACT_CUSTOMER")
public abstract class AbstractCustomer implements Serializable {

    @Id
    protected int customerId;
    protected String name;
    protected String type;

In Listing 8.34, you will notice that the Residential subclass is mapped to its own table. Notice the use of the @PrimaryKeyJoinColumn annotation to link the primary key to the superclass. The Business Customer class will be similar.

Listing 8.34. Mapping Subclass with the Joined Strategy

@Entity
@Table(name = "RESIDENTIAL_CUSTOMER")
@PrimaryKeyJoinColumn(name="CUSTOMER_ID",
referencedColumnName="CUSTOMER_ID")

public class ResidentialCustomer
extends AbstractCustomer implements Serializable {
    protected short householdSize;
    protected boolean frequentCustomer;

The joined strategy has the following advantages:

  • Using joined subclass tables results in the most normalized database schema, meaning the schema with the least spurious or redundant data.
  • As more subclasses are added to the data model over time, the only schema modification that needs to be made is the addition of corresponding subclass tables in the database (rather than having to change the structure of existing tables).

Relations to a base class using this strategy can be loaded through standard joins and can use standard foreign keys, as opposed to the machinations required to load polymorphic relations to Table-per-Class base types, described next. The joined strategy is often the slowest of the inheritance models, unless provisions are made to "lazily load" levels of the hierarchy. Retrieving any subclass can require one or more database joins, and storing subclasses can require multiple INSERT or UPDATE statements.

Table-per-Class Strategy

The Table-per-Class strategy is what we defined as the Concrete Table Inheritance Strategy in Chapter 3. In this model, each concrete subclass will have its own table, and the superclass information is repeated in each of the tables. Listing 8.35 shows how you would configure the AbstractCustomer superclass with the TABLE_PER_CLASS strategy option. All that is needed is setting the inheritance type because the class is Abstract Class.

Listing 8.35. Mapping Superclass with Table-per-Class

@Entity
@Inheritance(strategy=TABLE_PER_CLASS)
public abstract class AbstractCustomer implements Serializable {

    @Id
    protected int customerId;
    protected String name;
    protected String type;

Listing 8.36 shows both subclasses, each mapping to its corresponding table.

Listing 8.36. Mapping Subclass with Table-per-Class

@Entity
@Table(name = "RESIDENTIAL_CUSTOMER")
public class ResidentialCustomer
extends AbstractCustomer implements Serializable {
    protected short householdSize;
    protected boolean frequentCustomer;

...

@Entity
@Table(name = "BUSINESS_CUSTOMER")
public class BusinessCustomer
extends AbstractCustomer implements Serializable {
    protected boolean volumeDiscount;
    protected boolean businessPartner;

As mentioned in Chapter 3, you need a way to manage the primary keys across the various concrete classes. Some databases support the notion of a sequence to generate keys across tables.

The Table-per-Class strategy is very efficient when operating on instances of a known class. Under these conditions, the strategy never requires joining to superclass or subclass tables. Reads, joins, inserts, updates, and deletes are all efficient in the absence of polymorphic behavior. Also, as in the joined strategy, adding new classes to the hierarchy does not require modifying existing class tables, as is required in the Single-Table strategy.

Polymorphic relations to nonleaf classes in a Table-per-Class hierarchy have many limitations. When the concrete subclass is not known, the related object could be in any of the subclass tables, making joins through the relation impossible. This ambiguity also affects identity lookups and queries; these operations require multiple SQL SELECTs (one for each possible subclass), or a complex UNION.

Table-per-Class inheritance mapping has the following limitations:

  • You cannot traverse polymorphic relations to nonleaf classes in a Table-per-Class inheritance hierarchy in queries.
  • You cannot map a one-sided polymorphic relation to a nonleaf class in a Table-per-Class inheritance hierarchy using an inverse foreign key.
  • You cannot use an order column in a polymorphic relation to a nonleaf class in a Table-per-Class inheritance hierarchy mapped with an inverse foreign key.
  • Table-per-Class hierarchies impose limitations on eager fetching. We will discuss fetching later in the section on "Tuning Options."

A more serious issue with the Table-per-Class strategy is what happens when a non-leaf (superclass) in the hierarchy is changed. In this case, every concrete class that inherits from that class—either directly or indirectly—must change. Therefore, you should only use the Table-per-Class strategy when the hierarchy is relatively stable.

Keys

Any Entity instance is uniquely identified by an ID in JPA. The ID property is then mapped to the primary key. You can mark a field on your entity as an ID using the @Id annotation. Listing 8.37 shows an example.

Listing 8.37. ID Field

@Entity
public class Product implements Serializable {
    private static final long serialVersionUID = 2435504714077372968L;

    @Id
    protected int productId;

    protected BigDecimal price;
    protected String description;

...

It some cases, primary keys need to be generated at Entity Creation Time. OpenJPA supports a number of mechanisms to do this.

JPA includes the GeneratedValue annotation for this purpose. It has the following properties:

  • GenerationType.AUTO—The default. Assign the field a generated value, leaving the details to the JPA vendor.
  • GenerationType.IDENTITY—The database will assign an identity value on insert.
  • GenerationType.SEQUENCE—Use a datastore sequence to generate a field value.
  • GenerationType.TABLE—Use a sequence table to generate a field value.

OpenJPA also offers two additional generator strategies for non-numeric fields, which you can access by setting strategy to AUTO (the default), and setting the generator string to one of the following:

  • uuid-string—OpenJPA will generate a 128-bit UUID unique within the network, represented as a 16-character string. For more information on UUIDs, see the IETF UUID draft specification at www1.ics.uci.edu/~ejw/authoring/uuid-guid/.
  • uuid-hex—Same as uuid-string, but represents the UUID as a 32-character hexadecimal string.

Listing 8.38 shows an example of using the Identity mapping. In this case, OpenJPA will defer to the database's implementation of generating an identity. The Sequence and Identity columns require that your database support these features.

Listing 8.38. Generating an ID Using the Identity Strategy

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {
    private static final long serialVersionUID = 7779370942277849463L;


    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ORDER_ID")
    protected int orderId;
    protected BigDecimal total;

For the Table Generator Strategy, you must define a table in the database. (The OpenJPA documentation contains details on the schema for this table.) Listing 8.39 shows an example of using the Table Strategy. In this case, you define a specific generator that points to a table. Then you point your generated strategy to the table. The Sequence would work in a very similar fashion. Sequence and table generators usually work better when you have to define an ID across several tables. For example, when using the Table-per-Concrete method of mapping Inheritance, all of your subclasses may need to share a common sequence or generator to ensure data integrity across instances.

Listing 8.39. Generating an Identity with the Table Strategy

@Entity
@Table(name="CUSTOMER")
public class Customer {

    @Id
    @GeneratedValue(
        strategy=GenerationType.TABLE, generator="AuthorGen"
    )
    @TableGenerator(
        name="AuthorGen", table="AUTH_GEN", pkColumnName="PK",
        valueColumnName="AID"
    )
    @Column(name="AID", columnDefinition="INTEGER64")
    private long id;

    ...
}

The JPA specification requires you to declare one or more identity fields in your persistent classes. OpenJPA fully supports this form of object identity, called application identity. OpenJPA, however, also supports datastore identity. In datastore identity, you do not declare any primary key fields. OpenJPA manages the identity of your persistent objects for you through a surrogate key in the database.

You can control how your JPA datastore identity value is generated through OpenJPA's org.apache.openjpa.persistence.DataStoreId class annotation. This annotation has strategy and generator properties that mirror the same-named properties on the standard javax.persistence.GeneratedValue annotation just described.

To retrieve the identity value of a datastore identity entity, use the OpenJPAEntity Manager.getObjectId(Object entity) method. Listing 8.40 shows an example of using this method.

Listing 8.40. Using an Application Managed Identity in OpenJPA

import org.apache.openjpa.persistence.*;

@Entity
@DataStoreId
public class LineItem {

    ... no @Id fields declared ...
}

If you choose to use application identity, you may want to take advantage of OpenJPA's application identity tool. The application identity tool generates Java code implementing the identity class for any persistent type using application identity. The code satisfies all the requirements the specification places on identity classes. You can use it as-is, or simply use it as a starting point, editing it to meet your needs. Refer to the OpenJPA documentation for more details.

When your entity has multiple identity fields, at least one of which is a relation to another entity, you must use an identity class. You cannot use an embedded identity object. Identity class fields corresponding to entity identity fields should be of the same type as the related entity's identity.

Your identity class must meet the following criteria:

  • The class must be public.
  • The class must be serializable.
  • The class must have a public no-args constructor.
  • The names of the nonstatic fields or properties of the class must be the same as the names of the identity fields or properties of the corresponding entity class, and the types must be identical.
  • The equals and hashCode methods of the class must use the values of all fields or properties corresponding to identity fields or properties in the entity class.
  • If the class is an inner class, it must be static.
  • All entity classes related by inheritance must use the same identity class, or else each entity class must have its own identity class whose inheritance hierarchy mirrors the inheritance hierarchy of the owning entity classes.

Listing 8.41 shows an example of an ID class that can be used as an Identity. This class can be used then in the find operation as input.

Listing 8.41. An ID class in OpenJPA

@Embeddable
public class LineItemId implements Serializable{
    private static final long serialVersionUID =
        2160402020032769707L;

    private int orderId;
    private int productId;

    //getters and setters
    @Override
    public int hashCode() {
        //calculate hash
    }
    @Override
    public boolean equals(Object obj) {
        //implement equals
    }
}

After you do this, you can use the @IdClass annotation to specify the class, as shown in Listing 8.42. The ID fields on the Entity must match that on the ID class.

Listing 8.42. IdClass Annotation Usage

@Entity
@Table(name="LINE_ITEM")
@IdClass(LineItemId.class)
@NamedQuery(
    name="existing.lineitem.forproduct",
    query="select l from LineItem l
           where l.productId = :productId
           and l.orderId = :orderId"
)
public class LineItem implements Serializable {

    @Id
    @Column(name="ORDER_ID")
    private int orderId;

    @Id
    @Column(name="PRODUCT_ID")
    private int productId;

Alternatively, you can use the @EmbeddedId annotation and have the ID class as a member of the entity. This is assuming the ID class is annotated as embeddable, as in Listing 8.40. We will discuss embeddable classes later in the chapter. Listing 8.43 shows an alternative implementation of the LineItem Entity with the EmbeddedId.

Listing 8.43. Embeddable ID

@Entity
@Table(name="LINE_ITEM")
public class LineItem implements Serializable {

    @EmbeddedId
    private LineItemId lineItemId;

The JPA specification limits identity fields to simple types. OpenJPA, however, also allows ManyToOne and OneToOne relations to be identity fields. To identify a relation field as an identity field, simply annotate it with both the @ManyToOne or @OneToOne relation annotation and the @Id identity annotation. Listing 8.44 shows an example of how this looks.

Listing 8.44. Entities as ID

@Entity
@IdClass(LineItemId.class)
public class LineItem {

    @Id
    private int lineItemId;

    @Id
    @ManyToOne
    private Order order;

    ...
}

OpenJPA allows you to specify an ID through XML mapping as well. For details, we again refer you to its extensive documentation [OpenJPA 2].

Attributes

A field on an Entity is a primitive Java type. By default, all fields on an Entity are persistent. Therefore, all you have to do is declare a POJO as an entity and define its Ids. Listing 8.45 shows an example of a persistent Product class.

Listing 8.45. Persistent Fields

@Entity
public class Product implements Serializable {
    @Id
    protected int productId;

    protected BigDecimal price;
    protected String description;
    //getters and setters
}

Alternatively, you can use the @Basic annotation to denote that a field is persistent. That @Basic annotation is usually not used because fields are persistent by default; however, if you need to override the default handling, you can use it. You will see an example later. To map the field to a column, you can use the @Column annotation. An example is shown in Listing 8.46. Notice that you do not have to specify a column annotation for attributes whose name matches that of the column. OpenJPA will map each field to the column with the same name.

Listing 8.46. Mapping Columns

public class LineItem implements Serializable {

    @Id
    @Column(name="ORDER_ID")
    private int orderId;

    @Id
    @Column(name="PRODUCT_ID")
    private int productId;
    ...

You can express the same mappings using XML, as shown in Listing 8.47.

Listing 8.47. XML Mapping for Attributes

<entity-mappings>

    <entity class="Order">
        <attributes>
            <id name="orderId">
                <column name="ORDER_ID"/>
            </id>
            <basic name="total"/>
            <basic name="tax">
                <column name="TAX_FIELD">
            </basic>
        </attributes>
    </entity>
...
</ entity-mappings>

Fields that you do not want to persist can be marked as transient using the @Transient annotation. An example is shown in Listing 8.48. It is left up to the developer to populate this field. We will discuss derived fields later.

Listing 8.48. Declaring a Field to be Transient

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {

    @Id
    protected int orderId;
    protected BigDecimal total;
    protected BigDecimal tax;
    @Transient
    protected BigDecimal totalAndTax;

The JPA specification defines default mappings between Java Types and Database types. It will handle most basic conversions between Strings and VARCAR and even Strings to number types. The @Column annotation and XML equivalent have additional attributes to customize the mapping.

  • String name—The column name. Defaults to the field name.
  • String columnDefinition—The database-specific column type name. This property is used only by vendors that support creating tables from your mapping metadata. During table creation, the vendor will use the value of the columnDefinition as the declared column type. If no columnDefinition is given, the vendor will choose an appropriate default based on the field type combined with the column's length, precision, and scale.
  • int length—The column length. This property is typically used only during table creation, though some vendors might use it to validate data before flushing. CHAR and VARCHAR columns typically default to a length of 255; other column types use the database default.
  • int precision—The precision of a numeric column. This property is often used in conjunction with scale to form the proper column type name during table creation.
  • int scale—The number of decimal digits a numeric column can hold. This property is often used in conjunction with precision to form the proper column type name during table creation.
  • boolean nullable—Whether the column can store null values. Vendors may use this property both for table creation and at runtime; however, it is never required. Defaults to true.
  • boolean insertable—By setting this property to false, you can omit the column from SQL INSERT statements. Defaults to true.
  • boolean updatable—By setting this property to false, you can omit the column from SQL UPDATE statements. Defaults to true.
  • String table—Sometimes you will need to map fields to tables other than the primary table. This property allows you to specify that the column resides in a secondary table. We will see how to map fields to secondary tables later in the chapter.

The JPA specification also helps deal with more complicated mapping like dates. The @Temporal annotation allows you to map a Java Date field to the appropriate database date type. Listing 8.49 shows an example of mapping a Java Date to a TIMESTAMP using the @Temporal annotation.

Listing 8.49. Declaring a Date Field to be Temporal

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {

    @Id
    protected int orderId;
    protected BigDecimal total;
    protected BigDecimal tax;
    @Transient
    protected BigDecimal  totalAndTax;
    @Temporal(TemporalType.TIMESTAMP)
    protected java.util.Date orderCreated;

OpenJPA also supports mapping fields to CLOB or BLOB columns using the @Lob annotation. Listing 8.50 shows an example of using @Lob to map a JPEG of the picture into a database column.

Listing 8.50. Declaring a Lob Mapping Type

@Entity
public class Product implements Serializable {
    @Id
    protected int productId;
    protected String name;
    protected String description;
    @Lob
    protected JPEG picture;

In Java 5, you can use Enumerations. You can mark your mapping to say which value (ordinal or String) you want to persist. Listing 8.51 shows how you can mark the status enumeration to be treated as the String value in the mapping.

Listing 8.51. Declaring Enumerated Field Mapping Types

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ORDER_ID")
    protected int orderId;
    protected BigDecimal total;

    public static enum Status { OPEN, SUBMITTED, CLOSED }
    @Enumerated(EnumType.STRING)
    protected Status status;

The default is Ordinal, but you can change the default value using OpenJPA-specific settings.

JPA supports customizing the fetch option of a field as well. You can fetch any field eagerly (loaded when the object is loaded) versus lazy (loaded when a field is accessed on the managed object). Eager is the default setting. Listing 8.52 shows an example of mapping the JPEG field with a fetch pattern of lazy.

Listing 8.52. Declaring the Fetch style

@Entity
public class Product implements Serializable {

    @Id
    protected int productId;
    protected String name;
    protected String description;

    @Lob
    @Basic(fetch=FetchType.Lazy)
    protected JPEG picture;

OpenJPA provides an @ExternalValues annotation for extending the default mapping. In addition, you can create custom types. See the OpenJPA documentation for more details.

Contained Objects

JPA supports contained objects, which are called embedded objects. For example, suppose there is an Address object associated with a Customer object in our domain model, but the database has only the Customer table with the address fields in it. Figure 8.4 shows this mapping.

Figure 8.4

Figure 8.4 Component mapping.

When creating the Address class, you would annotate it as @Embeddable. You can add column mappings on that class as well. Listing 8.53 shows the Address class.

Listing 8.53. Marking an Object as Embeddable

@Embeddable
public class Address implements Serializable{
    @Column(name="ADDRESS_LINE_1")
    private String addressLine1;

    @Column(name="ADDRESS_LINE_2")
    private String addressLine2;
    private String city;
    private String state;
    private String country;
    private String zip;

Then you can declare an address instance as a member of the class and mark it as Embedded, as shown in Listing 8.54.

Listing 8.54. Embedding an Embeddable Object

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    ...
    @Embedded
    protected Address address;

The Embedded annotation also allows you to override the mapping in case you want to embed the Address into another object where the column names in the associated relational table are likely different. You would use special override annotations to do this; refer to the OpenJPA specification for more details.

OpenJPA supports the reverse scenario as well—in which you may have a single object but multiple tables. Suppose that this time your domain model has a Customer object with the address information declared as properties rather than as a separate Address object, but in the relational database there were a CUSTOMER table and an ADDRESS table. Figure 8.5 shows the mapping.

Figure 8.5

Figure 8.5 Secondary table.

In this case, the ADDRESS table's primary key is also a foreign key to the CUSTOMER table. The mapping for the Customer object will look as shown in Listing 8.55. In JPA, you use the @SecondaryTable annotation to denote the address table. Then you add the table attribute to each @Column annotation you want mapped to the secondary table. You can have several secondary tables allowing you to map several tables that share a primary key to a single object.

Listing 8.55. Secondary Table Annotation

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@SecondaryTable(name="ADDRESS")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    @Column(name="ADDRESS_LINE_1",table="ADDRESS")
    private String addressLine1;

    @Column(name="ADDRESS_LINE_2",table="ADDRESS")
    private String addressLine2;

    ...

Relationships

OpenJPA supports mapping one-to-one, one-to-many, many-to-one, and many-to-many relationships. It allows relationships to be either unmanaged (unidirectional) or managed (bidirectional). The annotations or XML match the type of relationship: @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany. Without any database mappings, the default mapping will use a naming convention for foreign keys. However, you can specify the keys by using the @JoinColumn, as shown in Listing 8.56. This listing shows an example of a One-to-One relationship between Customers. You can also define the fetch behavior much like you can with a field, and the behavior for operations against the root object. Cascading operations were shown earlier in the chapter.

Listing 8.56. One-to-One Relationship

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    @OneToOne(
        fetch=FetchType.EAGER,
        cascade = {CascadeType.MERGE,
                 CascadeType.REFRESH},
        optional=true
    )
    @JoinColumn(name="OPEN_ORDER", referencedColumnName = "ORDER_ID")
    protected Order openOrder;

In our example, the Customer also has a one-to-many relationship with all the Orders, as shown in Listing 8.57. You will notice that no Join Column is specified. Instead, the mappedBy attribute is used to define a bidirectional relationship.

Listing 8.57. One-to-Many Relationship

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    @OneToOne(
        fetch=FetchType.EAGER,
        cascade = {CascadeType.MERGE,
                  CascadeType.REFRESH},
        optional=true
    )
    @JoinColumn(name="OPEN_ORDER", referencedColumnName = "ORDER_ID")
    protected Order openOrder;

    @OneToMany(mappedBy="customer",fetch=FetchType.LAZY)
    protected Set<Order> orders;

Listing 8.58 shows the other side of the relationship. The Order has a reference to Customer and it has a many-to-one relationship. Notice it defines all the metadata for the bidirectional relationship.

Listing 8.58. Many-to-One Relationship

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {
    private static final long serialVersionUID = 7779370942277849463L;


    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ORDER_ID")
    protected int orderId;
    protected BigDecimal total;

    public static enum Status { OPEN, SUBMITTED, CLOSED }
    @Enumerated(EnumType.STRING)
    protected Status status;

    @ManyToOne
    @JoinColumn(
        name="CUSTOMER_ID", referencedColumnName="CUSTOMER_ID"
    )
    protected AbstractCustomer customer;

Listing 8.59 shows Order's one-to-many relationship to LineItems. This is a unidirectional relationship because LineItem does not have a reference to Order. In this case, notice the use of a special @ElementJoinColumn. This is an OpenJPA extension that allows you to define the metadata for a one-to-many one-way mapping.

Listing 8.59. One-to-Many Relationship

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {

    // Removing code that is the same as in Listing 8.58

    @OneToMany(cascade=CascadeType.REMOVE,fetch=FetchType.EAGER )
    @ElementJoinColumn(
        name="ORDER_ID", referencedColumnName="ORDER_ID"
    )
    protected Set<LineItem> lineitems;

Another way to map relationships in OpenJPA is through a join table, as described in Chapter 3. This is actually used for many-to-many relationships, but it can also be used for one-to-many and many-to-one relationships. As a matter of fact, join tables are the only implementation of unidirectional one-to-many relationships that the JPA specification demands for compliance. In Chapter 3 we show a PRODUCT_CATEORGY table that defines both keys for the product and category tables. A Product can belong to different categories, and a category groups many products.

Listing 8.60 shows a bidirectional relationship between products and categories.

Listing 8.60. Many-to-Many Relationship

@Entity
@NamedQuery(name="product.all",query="select p from Product p")
public class Product implements Serializable {

    @Id
    @Column(name="PRODUCT_ID")
    protected int productId;

    protected BigDecimal price;
    protected String description;


    @ManyToMany
    @JoinTable(name="PRODUCT_CATEGORY",
            joinColumns={@JoinColumn(name="PRODUCT_ID")},
            inverseJoinColumns={@JoinColumn(name="CAT_ID")}
    )
    protected Collection<Category> categories;

...


@Entity
public class Category implements Serializable {

    @Id
    protected int CAT_ID;
    protected String name;

    @ManyToMany(mappedBy="categories")
    protected Collection<Product> products;

    public int getCAT_ID() {
        return CAT_ID;
    }

...

Collection-based Entities can also be ordered using the @OrderBy annotation, as shown in Listing 8.61.

Listing 8.61. Order Constraint

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    @OrderBy("status")
    protected Collection orders;

Constraints

OpenJPA allows you to define constraints on various mappings as well as work with database constraints. Some constraints have special annotations, whereas others are attributes on another annotation. Listing 8.62 shows an example of a unique constraint that declares the field as suitable for a key.

Listing 8.62. Unique Constraint

@Entity
@Inheritance(strategy=SINGLE_TABLE)
@Table(name = "CUSTOMER")
@DiscriminatorColumn(name="TYPE", discriminatorType = STRING)
public abstract class AbstractCustomer implements Serializable {

    @Id
    @Column(name="CUSTOMER_ID")
    protected int customerId;
    protected String name;
    protected String type;

    @Unique
    protected String ssID;

Refer once again to the associated OpenJPA documentation for a list of other constraints supported [OpenJPA 2].

Derived Attributes

Earlier, we showed that we can make a field transient with annotations in the code or XML mapping file. Often, you need to calculate transient fields based on other persistent fields. To do this properly, you need to know when data is loaded and persisted to manage the state of the object. Although Entities are just POJOs in OpenJPA, you can define Entity Listener methods on the POJO, or attach an EntityListener class to the POJO. Listing 8.63 shows an example of marking a method with the @PostLoad annotation, which causes the method to be invoked after the data is loaded.

Listing 8.63. Life Cycle Methods

@Entity
@Table(name="ORDERS")
public class Order implements Serializable {


    @Id
    protected int orderId;
    protected BigDecimal total;
    protected BigDecimal tax;
    @Transient protected BigDecimal totalAndTax; //Not persisted

    @PostLoad
    protected void calculateTotal() {
        totalAndTax =
        total+tax;
    }
}

As you can see from Listing 8.63, the totalAndTax field will not be set until the object is loaded and the persistent fields have been set.

JPA also supports the following callbacks for life cycle events and their corresponding method markers:

  • PrePersist—Methods marked with this annotation will be invoked before an object is persisted. This could be used for assigning primary key values to persistent objects. This is equivalent to the XML element tag pre-persist.
  • PostPersist—Methods marked with this annotation will be invoked after an object has transitioned to the persistent state. You might want to use such methods to update a screen after a new row is added. This is equivalent to the XML element tag post-persist.
  • PostLoad—Methods marked with this annotation will be invoked after all eagerly fetched fields of your class have been loaded from the datastore. No other persistent fields can be accessed in this method. This is equivalent to the XML element tag post-load.
  • PreUpdate—This is the complement to PostLoad. While methods marked with PostLoad are most often used to initialize nonpersistent values from persistent data, methods annotated with PreUpdate are normally used to set persistent fields with information cached in nonpersistent data.
  • PostUpdate—Methods marked with this annotation will be invoked after changes to a given instance have been stored to the datastore. This is useful for clearing stale data cached at the application layer. This is equivalent to the XML element tag post-update.
  • PreRemove—Methods marked with this annotation will be invoked before an object transactions to the deleted state. Access to persistent fields is valid within this method. You might use this method to cascade the deletion to related objects based on complex criteria, or to perform other cleanup. This is equivalent to the XML element tag pre-remove.
  • PostRemove—Methods marked with this annotation will be invoked after an object has been marked as to be deleted. This is equivalent to the XML element tag post-remove.

You can also externalize the callback methods to a different class using the @EntityListener annotation on the class. Refer to the OpenJPA documentation for more details.

  • + Share This
  • 🔖 Save To Your Account