Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Looking Forward To JPA 2.0 - Part 2

DZone's Guide to

Looking Forward To JPA 2.0 - Part 2

· Java Zone
Free Resource

Just released, a free O’Reilly book on Reactive Microsystems: The Evolution of Microservices at Scale. Brought to you in partnership with Lightbend.

In the second installment of this series of sneak peeks at JPA 2.0, I am making good on my promise to tell you a little about some of the new additions to the suite of object-relational mappings standardized in JPA. I must still retain my disclaimer from the first article (see “Looking Forward to JPA 2.0 – Part 1”), however, and say that the spec is not final, so none of what is included in this article has been fully reified.

The new object-relational mapping features fall into two main categories:
a) expand the modeling options that can be mapped
b) offer the ability to map existing models in more flexible ways

At this point the returning reader might feel a little confused, noticing that the first category is rather close to the topic of the last article. In truth, it actually does overlap, the reason having more to do with the fact that there were too many things to mention in the last article and not enough space to do it in, as well as the fact that these topics kind of span both and form a nice transition from one to the other. But I digress. Let’s move on to the features.

Additional Model Options to Map

Some of the limitations to what you can put in your domain object are due mainly to the time constraints that existed at the time the first version of the specification was being created. Some of the most commonly requested missing mappings were collections of basic types, collections of embedded objects, embedded relationships to other entities, and broader Map support.

Collections of Basic and Embeddable Objects

Collections of things are obviously extremely common, and even though it is possible in JPA 1.0 to have collections of entities, it is not surprising that people might also want to persist collections of non-entities, such as basic objects or even embeddables. To accommodate these two new cases a couple of new annotations were added, currently named @ElementCollection and @CollectionTable. The @ElementCollection annotation is used to indicate that the objects in the collection are stored in a collection table, and the @CollectionTable annotation allows the details of the collection table to be specified. If not explicitly included then default collection table values will apply.

Let’s use the example of a Vehicle that is equipped with a set of possible optional features, where each feature is an enumerated value of type FeatureType. A service visit is an embedded object that stores the date of the visit, the cost, and a description of the work that was done. The basic enumerated FeatureType and embeddable ServiceVisit types look like:

public enum FeatureType { AC, CRUISE, PWR, BLUETOOTH, TV /* … */ }

@Embeddable
public class ServiceVisit {
    @Temporal(DATE) 
    @Column(name=”SVC_DATE”)
    Date serviceDate;

    String workDesc;
    int cost;
}

Now we can use these types in our Vehicle entity, which may have zero or more optional features, and a service history (a list of all of the service visits).

@Entity
public class Vehicle {

    @Id int vin;

    @ElementCollection
    @CollectionTable(name=”VEH_OPTNS”)
    @Column(name=”FEAT”)
    Set<FeatureType> optionalFeatures;

    @ElementCollection
    @CollectionTable(name=”VEH_SVC”)
    @OrderBy(“serviceDate”)
    List<ServiceVisit> serviceHistory;
    …
}

 

The data model would look like:

[img_assist|nid=3257|title=|desc=|link=none|align=undefined|width=330|height=171]

The @Column annotation on the optionalFeatures mapping refers to the column that stores the feature type value, and as we can see in the data model, it will be in the VEH_OPTNS collection table, not the VEHICLE table.

Embedded Relationships


Sometimes an entity has a relationship to another entity, but the relationship is actually more conveniently stored as part of an embedded object within the source entity. Relationships were not previously allowed to be contained within embeddable objects, but in JPA 2.0 this restriction was lifted.

Going back to our Vehicle example we can add an embedded PurchaseInfo object which contains information like the purchase price, and the date of sale, but also a reference to the dealer that sold the vehicle. Our PurchaseInfo object might look like:

@Embeddable
public class PurchaseInfo {
    int price;

    @Temporal(DATE) 
    @Column(name=”PUR_DATE”)
    Date purchaseDate;

    @ManyToOne
    Dealer dealer;
}

The @ManyToOne relationship that exists in the PurchaseInfo is really just a relationship from Vehicle to Dealer, but it just so happens that the relationship is defined in the PurchaseInfo object that is embedded in Vehicle.

This relationship could be unidirectional, but we could just as easily make it bidirectional by mapping it on the Dealer side. If it were bidirectional then the Dealer would refer to the target entity as Vehicle, and point to the dealer attribute through the purchaseInfo attribute of Vehicle. The relevant Vehicle and Dealer code is below.

@Entity
public class Vehicle {

    @Id int vin;

    @Embedded
    PurchaseInfo purchaseInfo;
    …
}

@Entity
public class Dealer {

    @Id int dealerId;

    @OneToMany(mappedBy=”purchaseInfo.dealer”)
    Collection<Vehicle> vehiclesSold;
    …
}

Maps and more Maps

Maps can currently be used to contain entities in one-to-many and many-to-many relationships, but the keys must be primary key attributes, or otherwise unique attributes of the target entities. It would be nice to be able to store the entities keyed by a basic type, or simply be able to use a Map to store basic typed values.

Maps may now contain any combination of basic types, embeddable objects and entities as keys or values. This offers significant flexibility and opens the door to using almost completely arbitrarily typed maps in the entity model. Because of space limitations I couldn’t possibly show examples of all possible combinations, but I’ll take a couple of configurations and demonstrate how they can be used, and hope you get the basic idea.

In our previous code example our PurchaseInfo object, embedded in Vehicle, contained a many-to-one relationship to Dealer, and Dealer had a one-to-many relationship back to the Vehicle. If we wanted to actually see the purchase info from the Dealer side it would actually make sense to have a Map of sold vehicles keyed by the PurchaseInfo. To achieve this our Dealer entity would look like:

@Entity
public class Dealer {

    @Id int dealerId;

    @OneToMany(mappedBy=”purchaseInfo.dealer”)
    Map<PurchaseInfo,Vehicle> vehiclesSold;

    @ElementCollection
    @CollectionTable(name=”VEH_INV”,
                     joinColumns=@JoinColumn(name=”DLR_ID”))
    @MapKeyJoinColumn(name=”V_ID”)
    @Column(name=”COUNT”)
    Map<Vehicle,Integer> inventory;
    …
}

In a bidirectional one-to-many target foreign key relationship the key of the relationship is stored in the target table. In our case the key is just an embedded object (in the target table) so the mapped values of the embeddable class are applied to obtain the PurchaseInfo object from the VEHICLE table. As a cautionary note, the PurchaseInfo object that is embedded in a Vehicle is not necessarily identical to the key instance in the vehiclesSold Map. These are not first class entities, and thus do not qualify to have their identity uniquely managed.

The second collection is a Map that models the vehicle inventory, keeping track of the number of each type of vehicle in stock. The key is a Vehicle entity and the value is a simple integer, so it is an element collection that is stored in a collection table.

The @MapKeyJoinColumn and @Column annotations designate the columns used to store the key and value for each map entry and apply to the collection table. The @MapKeyJoinColumn annotation is only used when the Map key is an entity and indicates the foreign key column referencing the primary table of that entity. The @Column annotation indicates the column containing the Map value, and in this case we are storing the inventory value in the “COUNT” column. Assuming the above annotations we are specifying a collection table that looks like the following:

 


Mapping Models More Flexibly

There are times when the ideal object model must map to a pre-existing data model that does not fit perfectly with the mappings available in JPA 1.0. The flexibility to use alternative data models adds power and applicability to the API.


Unary Join Table


A unidirectional many-to-one or one-to-one relationship is almost always modelled by a foreign key in the primary table of the source entity. In the case of a bidirectional one-to-one it may of course be in the primary table of the target entity when looking at it from the other side of the relationship. On rare occasions a separate join table may be used to store the foreign key. As a result, it was necessary to permit designating a separate join table for this purpose and not require it to be in the table of one or the other entity. Let’s go back to our Vehicle entity and try this out.

We may have a table that links a vehicle to the individual to whom the vehicle is registered. By adding a one-to-one relationship between Vehicle and Owner, and specifying a @JoinTable annotation in addition to the @OneToOne annotation on the owner relationship, then the default join table values will apply.

Unidirectional One-to-Many Foreign Key

The most reasonable and common way to model a one-to-many/many-to-one relationship is for the relationship to be bidirectional and to store a foreign key on the many-to-one side. However, in some obscure cases, some have asked for the ability to have a unidirectional one-to-many relationship, but keep the foreign key on the many-to-one side. This would be equivalent to, say, having a one-to-many relationship from Vehicle to its parts, but not having any relationship from Part back to Vehicle, even though the foreign key column exists in the PART table. Whenever a part is added to the list of parts for a vehicle then the row corresponding to that part in the PART table must be updated, even though the Part entity knows nothing about the vehicle it is a part of.

The problem reduces to the fact that there is relationship metadata (a foreign key) being stored with Part instances, even though the Part entity does not really participate in that relationship. Nevertheless, the mapping has been added, and the join table that is normally required for unidirectional one-to-many mappings is now optional. The way to identify a mapping such as the one we are describing is to annotate the relationship with the @JoinColumn annotation. If the @JoinColumn name attribute is omitted the default join column rules will apply.

@Entity
public class Vehicle {

    @Id int vin;

    @OneToMany
    @JoinColumn(name=”V_ID”)
    Collection<Part> parts;
    …
}

The absence of the mappedBy attribute in the @OneToMany annotation indicates that the Vehicle entity owns the relationship, i.e. that it is unidirectional, and the presence of @JoinColumn indicates that the foreign key is in the target PART table and not in a join table.

Summary

You are now experts in the object-relational aspects of JPA 2.0 mappings and can impress your friends by saying things like: “I can’t wait until JPA 2.0 comes out, when I can use a collection table to store my map of basic objects keyed by embeddables.” At that point you just walk away and pretend that what you were talking about was so incredibly valuable that a person would have to be crazy not to use it.

Next time round we will look at the query enhancements and how expressions can be used in JPA to create dynamic, object-oriented queries. Stay tuned!

For further background information read part 1 of this article series.


 

Strategies and techniques for building scalable and resilient microservices to refactor a monolithic application step-by-step, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}