The mapping arsenal grew dramatically in JPA 2.0 to include mappings that are less common, but were not defined in the first release. A number of mappings were added to provide better support for reading from pre-existing (so-called “legacy”) database schemas. While these mappings are not hugely useful to the average application, they come in handy to those who must work with a schema that is outside the control of the Java developer.
Element Collections
Arguably, the most useful of the new mapping types is the element collection, which allows an entity to reference a collection of objects that are of a basic type (such as String or Integer). The @ElementCollection annotation is used to indicate the mapping. The objects are stored in a separate table called a collection table, which defaults to be named <entityName>_<attributeName>, but can be overridden with the @CollectionTable annotation. The name of the column in the collection table that stores the values defaults to the name of the attribute but is overridable with the @Column annotation.
Element collection of String:
@Entity
public class Person {
…
@ElementCollection
@CollectionTable(name=”NICKNAMES”)
@Column(name=”NNAME”)
Collection<String> nicknames;
…
}
Element collection mappings can be used in embeddables and mapped superclasses as well as entities. They can also contain embeddable objects instead of basic types.
Adding a Join Table
Normally, a bidirectional one-to-many relationship is mapped through a foreign key on the “many” side. However, in some data schemas, the join is done using a separate join table. Similarly a unidirectional or bidirectional one-to-one relationship can use a join table instead of the typical simple foreign key. As one might expect, using a join table for these mappings is as easy as putting an additional @JoinTable annotation on the owning side of the mapped relationship.
Unidirectional One-to-Many With No Join Table
In JPA 1.0, a unidirectional one-to-many relationships required a join table. However, you were stuck if the schema was fixed and had a foreign key in the target table and the target entity was not able to be modified to have a reference back to the source entity.
The ability to do this was added in JPA 2.0 by permitting a @JoinColumn annotation to accompany a @OneToMany annotation that was unidirectional (did not contain a mappedBy element). The join column refers to a foreign key column in the target table, which points to the primary key of the source table.
Unidirectional one-to-many relationship with target foreign key:
@Entity
public class Customer {
…
@OneToMany
@JoinColumn(name=”CUST_ID”)
List<Purchase> purchases;
…
}
A unidirectional one-to-many target foreign key mapping may seem to make life easier in Java but performs worse than using a join table.
Orphan Removal
A parent-child relationship is a one-to-many or one-to-one relationship in which the target entity (the child) is owned by the source entity (the parent) or relies upon it for its existence. If either the parent gets deleted or the relationship from the parent to the child gets severed, then the child should be deleted. Previous support for cascading delete offered a solution for the first case; but to solve the second case when the child is left an orphan, the new orphanRemoval element of the @OneToMany or @OneToOne annotations can be set to true to cause the child object to get removed automatically by the provider.
Configuring and using orphan removal:
@Entity public class Library {
…
@OneToMany(mappedBy=”library”, orphanRemoval=true)
List<Magazine> magazines;
…
}
In the application code, when a magazine is torn and is to be discarded from the library, the act of removing the magazine from the library causes the magazine to automatically be deleted:
library.getMagazines().remove(magazine);
Persistently Ordered Lists
When a many-valued relationship causes fetching of the related entities in a List and the order of the entities must be determined by sorting one or more attributes of the target entity type, the relationship mapping needs to be annotated with @OrderBy. However, if the order is determined solely by the position of the entity in the in-memory list at the time the list was last persisted, then an additional column is required to store the position. This column is called an order column and is specified on the mapping by the presence of @OrderColumn. The entity’s position in the list is read from and written to this column.
Ordering without using an object attribute:
@Entity public class WaitList {
…
@OneToMany
@OrderColumn(name=”POSITION”)
List<Customer> customer;
…
}
Every time a transaction causes a customer to be added to or removed from the waiting list, the updated list ordering will be written to the order column. Because the example is using a unidirectional one-to-many relationship that defaults to using a join table the order column will be in the join table.
Maps
Using a Map for a many-valued relationship traditionally meant that entities were the values and some attribute of the entities was the key. The new Map features allow Map types to be used with keys or values that can be basic types, entities, or embeddables. When the value is an entity type, then the mapping is a relationship. When the value is a basic or embeddable type, the mapping is an element collection. Two examples of using a Map are shown. The deliverySchedule attribute is an element collection of dates on which deliveries are made to given address strings. The @MapKeyColumn annotation indicates the column in the collection table where the keys (address strings) are stored, and @Column references the column in which the date values are stored. The @Temporal annotation is used because the values are Date objects.
The suppliers attribute illustrates a unidirectional one-to-many relationship that has a Map of Supplier entities keyed by Part entities. The @MapKeyJoinColumn annotation indicates the join column in the join table referring to the Part entity table.
Element collection Map and one-to-many Map:
@Entity public class Assembly {
…
@ElementCollection
@CollectionTable(name=”ASSY_DLRVY”)
@MapKeyColumn(name=“ADDR”)
@Column(name=“DLRVY_DATE”)
@Temporal(TemporalType.DATE)
Map<String, Date> deliverySchedule;
…
@OneToMany
@JoinTable(name=”PART_SUPP”)
@MapKeyJoinColumn(name=”PART_NO”)
Map<Part, Supplier> suppliers;
…
}
The main lesson is that different annotations are useful and applicable depending upon the key and value types in the Map.
Derived Identifiers
New allowances were made in JPA 2.0 for the case when an entity has a compound primary key that includes a foreign key. Multiple @Id annotations can be specified, and they can be on an owned one-to-one or many-to-one mapping. The foreign key of that mapping will be included in the id class, as shown in the example. The id class must still have a field for each of the primary key components and be named the same as the entity attributes, but the types of the foreign key-based id class fields must match the identifier types of the target entity of the relationship. For example, the dept field in the ProjectId class is not of type Department as it is in Project, but is of type int, which is the identifier type of the Department entity.
Derived Identifier:
@IdClass(ProjectId.class)
@Entity public class Project {
…
@Id
String projectName;
@Id @ManyToOne
Department dept;
…
}
@Entity public class Department {
…
@Id
int id;
…
}
public class ProjectId {
String projectName;
int dept;
…
}
Compound primary keys may also be composed of multiple foreign keys or relationships by adding @Id annotations to additional relationships in the entity and adjusting the id class accordingly.
Derived identifiers also support a multitude of additional identifier combinations involving embedded identifiers, multiple levels of compounding, and shared relationship primary keys.
Java Persistence Query Language
A number of additional JP QL features were added to support the new mappings, while other features were added simply as improvements to the query language. The table summarizes the changes.
New features of JP QL:
Feature Name |
Description |
Example |
Date, time, and timestamp literals |
JDBC syntax was adopted: {d ‘yyyy-mm-dd’} {t ‘hh-mm-ss’} {ts ‘yyyy-mm-dd hh-mm-ss’} |
SELECT c FROM Customer c WHERE c.birthdate < {d ‘1946-01-01’} |
Non-polymorphic queries – TYPE |
Can query across specific subclasses of a superclass |
SELECT p FROM Project p WHERE TYPE(p) = DesignProject OR TYPE(p) = QualityProject |
Map support - KEY, VALUE, ENTRY |
Allow comparison and selection of keys and values and selection of entries |
SELECT e.name, KEY(p), VALUE(p) FROM Employee e JOIN e.phones p WHERE KEY(p) IN (‘Work’, ‘Cell’) |
Collection input parameters |
Allow parameter arguments to be collections |
SELECT e FROM Employee e WHERE e.lastName IN :names |
CASE statement |
Can be in either of two forms:
- CASE {WHEN conditional THEN scalarExpr}+ ELSE scalarExpr END
- CASE pathExpr {WHEN scalarExpr THEN scalarExpr}+ ELSE scalarExpr END
|
UPDATE Employee e SET e.salary = CASE WHEN e.rating = 1 THEN e.salary * 1.1 WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END |
NULLIF, COALESCE |
Additional CASE variants: COALESCE(scalarExpr {, scalarExpr}+) NULLIF(scalarExpr, scalarExpr |
SELECT COALESCE(d.name, d.id) FROM Department d |
Scalar expressions in the SELECT clause |
Return the result of performing a scalar operation on a selected term |
SELECT LENGTH(e.name) FROM Employee e |
INDEX in a List |
Refer to an item’s position index in a list |
SELECT p FROM Flight f JOIN f.upgradeList p WHERE f.num = 861 AND INDEX(p) = 0 |
Variables in SELECT constructors |
Constructors in SELECT clause can contain identification vars |
SELECT new CustInfo(c.name, a) FROM Customer c JOIN c.address a |
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}