Using the Java Persistence API with Spring 2.0
by Seth White
03/20/2006
Relationships
Most entities have relationships to some other entity or entities in the domain model, and the Patient
class is no exception. Figure 2 shows a diagram of the Patient
class and its relationships.
Figure 2. Patient class relationships
JPA uses the OneToOne
, ManyToOne
, OneToMany
, and ManyToMany
annotations to specify relationships and their cardinality. Every relationship field must be annotated with one of these annotations. Patient
has a ManyToOne
relationship with Address
, for example. This means that many patients may have the same address. Patient
has a OneToOne
relationship with the User
class, which contains the patient's username and password information. This means that each Patient
is related to a single User
.
Patient
has a OneToMany
relationship with both Prescriptions
and Records
since a particular patient can have more than one prescription and may have made more than one visit to a doctor (which results in a medical record being created). Conversely, each Prescription
or Record
can be related to only one Patient
.
Cascade operations
An interesting feature of JPA relationships is cascading of operations. You may already be familiar with the "cascade delete" feature that most relational databases support. JPA takes the idea of cascading an operation like delete and applies it to other operations as well, such as insert and update. The Patient
class cascades insert and update operations to its related Address
and User
. Insert is referred to as persist in JPA since a database insert is performed when an object is made persistent. Update is referred to as merge since an update merges the changes in an object into the current JPA persistence context. Don't worry if this seems confusing; just remember to call merge when you want to update an object that is already persistent and to call persist to insert a new persistent object in the database.
Deciding whether or not to cascade a particular operation, such as persist, merge, or remove, can be tricky. In the case of the Patient
class, persist is cascaded to Address
and User
because the application also needs to insert a new Address
and User
whenever a new Patient
is inserted. Similarly, the Address
and User
may also need to be updated when a Patient
is updated. Prescriptions
and Records
work differently. The application has operations that explicitly persist (and update) a Prescription
or Record
, so there is no need to cascade these operations from Patient
to these classes.
You may remember that in a previous section of this article we said that multiple Patients
are allowed to refer to the same Address
—the relationship from Patient
to Address
is ManyToOne
. Yet, we also just said that Patient
cascades inserts to Address
, which results in the creation of a new address for each patient in the database. So, what gives? Well, what you see here is an interesting effect of using generated primary keys and cascade insert between Patient
and Address
. Because the primary keys for address are generated automatically by the database, every Address
will have a unique primary key, and it's possible for two different Address
objects that refer to the same real address to be inserted in the database. This is fine for many applications, including MedRec, but if you want to prevent duplicate addresses such as this, your DAO needs to perform some additional logic.
Web Services Considerations
The fact that MedRec's entity classes are mapped both to a database schema by JPA and to XML by the WebLogic Server 9.1 Web services implementation raised some additional issues that affected the design of the entity classes.
JPA supports both unidirectional and bidirectional relationships, but if you look at the entity classes in MedRec you will see that all the relationships are unidirectional. This was necessary because the Web services binding could not support bidirectional relationships.
Another problem arose because of JPA's requirement that Java Collection types, like java.util.Set
, be used for multivalued relationship fields. It turns out that the Web services implementation in 9.1 doesn't support Collection
types. So, how did we get around this?
Recall that the object-relational mapping can either be defined on the fields or on the methods of an entity. This flexibility on the part of JPA turned out to be very important. Web services, on the other hand, use the public property accessor methods ( getXXX()
methods) of a class to define the properties that are marshaled into XML. By defining the persistence mapping using fields and letting the XML binding be defined by the methods on the class, we were able to keep the two mappings separate! Multivalued relationships used a Set
internally for persistence but were exposed via the property access methods as an array that the XML binding could handle.
To make this concrete, here is an example of a property access method from the Record
class that performed the translation:
public Prescription[] getPrescriptions() {
if (prescriptions == null)
return null;
return (Prescription[]) prescriptions.toArray(new
Prescription[prescriptions.size()]);
}
Internally, the method accesses the persistent prescriptions field which has type java.util.Set
. The Set
is translated into an array that the Web services implementation can handle. Our experiences with Web services integration left us very impressed with JPA and its ability to integrate with an existing application and infrastructure.
This wraps up the discussion of persistent classes. It may seem like you need to follow a lot of rules and restrictions, but in practice most of the JPA rules simply embody good coding practice and you will probably find that you are following them without giving it a lot of thought.