Using the Java Persistence API with Spring 2.0

by Seth White
03/20/2006

JPA Persistent Classes

So far, we've seen some example data access code that handles querying and updating, but what about the persistent classes themselves? What do they look like? Think POJOs plus metadata. Persistent POJOs need to follow a number of rules or design patterns, however. Some of these rules are specified by JPA, while others are a consequence of the overall application architecture, as we shall see below. 

First a note about metadata. JPA gives developers the option of specifying JPA metadata related to persistence and object-relational mapping in either an external XML file, Java 5.0 annotations, or a combination of the two. The medical records application uses Java annotations for JPA metadata. This has the advantage that the metadata is collocated with the Java classes, fields, and methods to which it applies, which can aid understanding. If you prefer to keep your metadata separate from your code, you can use XML instead.

Let's look at the Patient class and its field declarations to see what an example annotated JPA class looks like:


package com.bea.medrec.domain; 



import javax.persistence.*;

import kodo.persistence.jdbc.ElementJoinColumn;



@Entity()

public class Patient implements Serializable

{

  @Id()

  @GeneratedValue(strategy=IDENTITY)

  private Integer id;



  private Date dob;



  @Column(name = "first_name")

  private String firstName;



  private String gender;



  @Column(name = "last_name")

  private String lastName;



  @Column(name = "middle_name")

  private String middleName;



  private String phone;



  private String ssn;



  @ManyToOne(cascade={PERSIST, MERGE})

  @JoinColumn (name="address_id")

  private Address address;



  @OneToOne(cascade={PERSIST, MERGE})

  @JoinColumn (name="email")

  private User user;



  @OneToMany(targetEntity = Prescription.class)

  @ElementJoinColumn(name="pat_id")

  private Set prescriptions=null;



  @OneToMany (targetEntity=Record.class)

  @ElementJoinColumn(name="pat_id")

  private Set records =null; 



  ...

}

Notice that the Patient class is annotated with the Entity annotation. This is a requirement for all persistent classes that use annotations. The presence of the Entity annotation tells the persistence engine that this class is a JPA entity. Entity classes are not required to be public, in general, but MedRec's persistent classes need to be public so that the Web and Web services tiers can use them.

Entity classes also are not required to implement Serializable, but since the Patient class is placed into the HTTP session by the MedRec struts actions, it needs to be serializable. This is so that the application can work in a clustered environment where session state may be serialized between nodes in the cluster. Other requirements on the entity class are that it must be a top-level class and it must not be final. See the JPA specification (JSR220) for complete listing of requirements.

Persistent fields

JPA implementations need to read and write the state (persistent fields) of entity classes at runtime. The JPA specification allows implementations to do this either by directly accessing the fields of an entity or by calling JavaBeans-style accessor methods (getters/setters). The style of access that is used is determined by where the persistence annotations are placed. If they are placed on the fields of the class, then field access is used, for example.

Why does JPA support both field and method styles of access, you ask? The answer is flexibility. Some classes perform validation in their public accessor methods. This can cause problems for the JPA implementation if an exception is thrown because a value in the database is out of range. If your class performs validation in its setters, it's probably best to annotate the fields. On the other hand, if you want to support something like a virtual persistent property, then annotate the accessor methods.

In JPA entities, all non-transient fields that are not annotated with the Transient annotation (an annotation defined by JPA) are persistent. "Persistent" means that the field is mapped to a column in the database. Notice that some of the persistent fields in the Patient class have no annotations. This is because the defaults defined by JPA, like default column name, were correct for these fields. When the field name is different from the database column name to which it is mapped, you must use the Column annotation to specify a different column name for the database column.

Every entity must have a primary key. JPA supports both simple (single field) and composite (multifield) primary keys. It's a best practice to always use a simple primary key if you can, and to let the database generate the primary key value for you. This is because JPA doesn't allow the primary key value to ever be changed once an object is persisted. Most of the persistent classes in the medical records application, including the Patient class, use simple generated primary keys for this reason. If you want to see an example of a class that has a composite primary key, look at the Group class. The Id annotation marks the field that is the primary key, identity, or field.

The GeneratedValue annotation denotes the fact that the primary key value is generated by the database when a new Patient is inserted. The database can generate the primary key value in a number of ways, and the strategy attribute can be used to select one of these. The Patient class uses the identity strategy, which means that the id field should be mapped to a database auto-increment or identity column.

A subtle thing to be aware of when using the identity strategy is the effect this has on the equals and hashcode methods of your class. For example, if the equals method compares the values of the id field, make sure you don't use the object in a way that will cause the equals method to be invoked before the database insert happens—usually at transaction commit—because the primary key value won't be assigned until then. One way to avoid this is to use a natural key in the equals method if your class has one instead of using the generated key. For example, Patient Social Security number would be a good natural key to use in the Patient equals method.