Mapping Collection-Based Relationships
The one-to-many and many-to-many examples we've seen so far have used the java.util.Collection
and java.util.Set
types. The Java Persistence specification also allows you to represent a relationship with a java.util.List
or a java.util.Map
.
Ordered List-Based Relationship
The java.util.List interface can express collection-based relationships. You do not need any special metadata if you want to use a List rather than a Set or Collection type. (In this case, the List actually gives you a bag semantic, an unordered collection that allows duplicates). A List type can give you the additional ability to order the returned relationship based on a specific set of criteria. This requires the additional metadata that is provided by the @javax.persistence.OrderBy
annotation:
package javax.persistence;
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OrderBy
{
String value( ) default "";
}
The value( ) attribute allows you to declare partial EJB QL that specifies how you want the relationship to be ordered when it is fetched from the database. If the value( ) attribute is left empty, the List
is sorted in ascending order based on the value of the primary key.
Let's take the Reservation/Customer relationship, which is a many-to-many bidirectional
relationship, and have the customers attribute of Reservation return a List that is sorted alphabetically by the Customer entity's last name:
@Entity
public class Reservation implements java.io.Serializable {
...
private List<Customer> customers = new ArrayList<Customer>( );
...
@ManyToMany
@OrderBy
("lastName ASC")
@JoinTable(name="RESERVATION_CUSTOMER"),
joinColumns={@JoinColumn(name="RESERVATION_ID")},
inverseJoinColumns={@JoinColumn(name="CUSTOMER_ID")})
public List<Customer> getCustomers( ) { return customers; }
public void setCustomers(Set customers);
...
}
"lastName ASC" tells the persistence provider to sort the Customer's lastName in ascending order. You can use ASC for ascending order and DESC for descending order. You can also specify additional restrictions like @OrderBy('lastname asc, firstname asc"). In this case, the list will be ordered by lastname, and for duplicates last names, it will be ordered by first name.
List XML mapping
Let's look at the XML mapping for this type of relationship:
<entity-mappings>
<entity class="com.titan.domain.Reservation" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<many-to-many name="customers"
target-entity="com.titan.domain.Customer"
fetch="LAZY">
<order-by>lastName ASC</order-by>
<join-table name="RESERVATION_CUSTOMER">
<join-column name="RESERVATION_ID"/>
<inverse-join-column name="CUSTOMER_ID"/>
</join-table>
</many-to-many>
</attributes>
</entity>
...
</entity-mappings>
Map-Based Relationship
The java.util.Map
interface can be used to express collection-based relationships. In this case, the persistence provider creates a map with the key being a specific property of the related entity and the value being the entity itself. If you use a java.util.Map, you must use the @javax.persistence.MapKey
annotation:
package javax.persistence;
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface MapKey
{
String name( ) default "";
}
The name( ) attribute is the name of the persistent property that you want to represent the key field of the map object. If you leave this blank, it is assumed you are using the primary key of the related entity as the key of the map.
For an example, let's use a map to represent the one-to-many unidirectional
Customer/Phone relationship discussed earlier in this chapter:
@Entity
public class Customer implements java.io.Serializable {
...
private Map<String, Phone> phoneNumbers = new HashMap<String, Phone>( );
...
@OneToMany(cascade={CascadeType.ALL})
@JoinColumn(name="CUSTOMER_ID")
@MapKey(name="number")
public Map<String, Phone> getPhoneNumbers( ) {
return phoneNumbers;
}
public void setPhoneNumbers(Map<String, Phone> phones)
{
this.phoneNumbers = phones;
}
}
In this example, the phones property of Customer will return a java.util.Map where the key is the number property of the Phone entity and the value is, of course, the Phone entity itself. There is no extra column to keep the map key since the map key is borrowed from the Phone entity.
Map XML mapping
Here's what the XML mapping would be for this example:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-many name="phoneNumbers"
target-entity="com.titan.domain.Phone"
fetch="LAZY">
<cascade-all/>
<map-key name="number"/>
<join-column name="CUSTOMER_ID"/>
</one-to-many>
</attributes>
</entity>
...
</entity-mappings>
|