Sept. 9, 2009, 9:46 a.m.
posted by hashspark
The Seven Relationship TypesSeven types of relationships can exist between entity beans. There are four types of cardinality: one-to-one, one-to-many, many-to-one, and many-to-many. In addition, each relationship can be either unidirectional or bidirectional. These options seem to yield eight possibilities, but if you think about it, you'll realize that one-to-many and many-to-one bidirectional relationships are actually the same thing. Thus, there are only seven distinct relationship types. To understand relationships, it helps to think about some simple examples:
Note that these relations represent the navigability of your domain model. Using EJB QL, you'll be able to return even unmapped association (for example, return the Cruises made by a given ship even if the association has been mapped as many-to-one unidirectional from Cruise to Ship). Once again, the associations defined in the metadata represent the domain object navigation only. In this chapter, we will discuss how to specify relationships by applying annotations to your related entity beans. We will also discuss several different common database schemas, and you will learn how to map them to your annotated relationships. One-to-One Unidirectional RelationshipAn example of a one-to-one unidirectional relationship is one between our Customer entity and an Address entity. In this example, each Customer has exactly one Address, and each Address has exactly one Customer. Which bean references which determines the direction of navigation. While the Customer has a reference to the Address, the Address doesn't reference the Customer. The relationship is therefore unidirectionalyou can only go from the Customer to the Address, not the other way around through object navigation. In other words, an Address entity has no idea who owns it. Figure shows this relationship. One-to-one unidirectional relationship![]() Relational database schemaAs shown in Figure, one-to-one unidirectional relationships normally use a fairly typical relational database schema in which one table contains a foreign key (pointer) to another table. In this case, the CUSTOMER table contains a foreign key to the ADDRESS table, but the ADDRESS table doesn't contain a foreign key to the CUSTOMER table. This allows records in the ADDRESS table to be shared by other tables, a scenario explored in the "Many-to-Many Unidirectional Relationship" section, later in this chapter. One-to-one unidirectional relationship in RDBMS![]() Programming modelIn unidirectional relationships (navigated only one way), one of the entity beans defines a property that lets it get or set the other bean in the relationship. Thus, inside the Customer class, you can call the getAddress( )/setAddress( ) methods to access the Address entity, but there are no methods inside the Address class to access the Customer. Let's look at how we would mark up the Customer bean class to implement this one-to-one relationship to Address:
package com.titan.domain;
@Entity
public class Customer implements java.io.Serializable {
...
private Address address;
...
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn
(name="ADDRESS_ID")
public Address getAddress( ) {
return homeAddress;
}
public void setAddress(Address address) {
this.homeAddress = address;
}
A one-to-one relationship is specified using the @javax.persistence.OneToOne annotation and is mapped with the @javax.persistence.JoinColumn annotation. Let's first look at the @JoinColumn annotation:
public @interface JoinColumn
{
String name( ) default "";
String referencedColumnName( ) default "";
boolean unique( ) default false;
boolean nullable( ) default true;
boolean insertable( ) default true;
boolean updatable( ) default true;
String columnDefinition( ) default "";
String table( ) default "";
}
The @JoinColumn annotation is pretty much the same as the @Column annotation. It defines the column in the Customer's table that references the primary key of the ADDRESS table in the schema we defined in Figure. If you are joining on something other than the primary-key column of the ADDRESS table, then you must use the referencedColumnName( ) attribute. This referencedColumnName( ) must be unique, since this is a one-to-one relationship. If you need to map a one-to-one relationship in which the related entity has a composite primary key, use the @JoinColumns annotation to define multiple foreign-key columns:
public @interface @JoinColumns
{
JoinColumn[] value( );
}
Now let's learn about the @OneToOne annotation:
public @interface OneToOne
{
Class targetEntity( ) default void.class;
CascadeType[] cascade( ) default {};
FetchType
fetch( ) default EAGER;
boolean optional( ) default true;
String mappedBy( ) default "";
}
The targetEntity( ) attribute represents the entity class you have a relationship to. Usually, you do not have to initialize this attribute, as the persistence provider can figure out the relationship you are setting up from the type of the property. The fetch( ) attribute works the same as we described in Chapter 6. It allows you to specify whether you want the association to be lazily or eagerly loaded. In Chapter 8, we'll show you how you can eagerly fetch a relationship with EJB QL, even when you have marked the FetchType as LAZY . The optional( ) attribute specifies whether this relationship can be null. If this is set to false, then a non-null relationship must exist between the two entities. The cascade( ) attribute is a bit complicated. We'll discuss it later in this chapter, as all relationship types have this attribute. The mappedBy( ) attribute is for bidirectional relationships and is discussed in the next section. The XML mapping has the same exact attributes as those for the annotation. Let's take a look:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-one name="address"
targetEntity="com.titan.domain.Address"
fetch="LAZY"
optional="true">
<cascade>ALL</cascade>
<join-column name="ADDRESS_ID"/>
</one-to-one>
</attributes>
</entity>
</entity-mappings>
Primary-key join columnsSometimes the primary keys of the two related entities are used instead of a specific join column. In this case, the primary keys of the related entities are identical, and there is no need for a specific join column. Figure shows that there is no specific foreign-key column that maps the relationship, as the tables are joined using the primary key. Primary-key joins![]() In this mapping scenario, you are required to use an alternative annotation to describe the mapping@javax.persistence.PrimaryKeyJoinColumn :
public @interface PrimaryKeyJoinColumn
{
String name( ) default "";
String referencedColumnName( ) default "";
String columnDefinition( ) default "";
}
The name( ) attribute refers to the primary-key column name of the entity the annotation is applied to. Unless your entity has a composite primary key, you can leave this blank and the persistence provider will figure it out. The referencedColumnName( ) is the column to join to on the related entity. If this is left blank, it is assumed that the related entity's primary key will be used. The columnDefinition( ) will be used when the persistence provider is generating schema and it will specify the SQL type of the referencedColumnName( ). If the primary-key join in question is of a composite nature, then the @javax.persistence.PrimaryKeyJoinColumns annotation is available to you:
public @interface PrimaryKeyJoinColumns
{
PrimaryKeyJoinColumn[] value( );
}
So, let's use this annotation to map the Customer/Address entity one-to-one relationship shown in Figure:
package com.titan.domain;
@Entity
public class Customer implements java.io.Serializable {
...
private Address homeAddress;
...
@OneToOne(cascade={CascadeType.ALL})
@PrimaryKeyJoinColumn
public Address getAddress( ) {
return homeAddress;
}
public void setAddress(Address address) {
this.homeAddress = address;
}
Since we're joining on the primary keys of the Customer and Address entities and they are not composite keys, we can simply annotate the address property of Customer with the defaulted @PrimaryKeyJoinColumn annotation. One-to-one unidirectional XML mappingThis is what the XML mapping would look like:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-one name="address"
targetEntity="com.titan.domain.Address"
fetch="LAZY"
optional="true">
<cascade-all/>
<primary-key-join-column/>
</one-to-one>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingIf your persistence provider supports auto schema generation, you do not need to specify metadata like @JoinColumn or @PrimaryKeyJoinColumn. Auto schema generation is great when you are doing fast prototypes:
package com.titan.domain;
@Entity
public class Customer implements java.io.Serializable {
...
private Address address;
...
@OneToOne
public Address getAddress( ) {
return homeAddress;
}
public void setAddress(Address address) {
this.homeAddress = address;
}
When you do not specify any database mapping for a unidirectional one-to-one relationship, the persistence provider will generate the necessary foreign-key mappings for you. In our Customer/Address relationship example, the following tables would be generated:
CREATE TABLE CUSTOMER
(
ID INT PRIMARY KEY NOT NULL,
address_id INT,
...
)
ALTER TABLE CUSTOMER ADD CONSTRAINT customerREFaddress
FOREIGN KEY (address_id) REFERENCES ADDRESS (id);
For unidirectional one-to-one relationships, the default mapping creates a foreign-key column named from a combination of the property you are mapping followed by an _ character concatenated with the primary-key column name of the referenced table. One-to-One Bidirectional RelationshipWe can expand our Customer entity to include a reference to a CreditCard entity, which maintains credit card information. The Customer will maintain a reference to its CreditCard, and the CreditCard will maintain a reference back to the Customerthis makes good sense, since a CreditCard should be aware of who owns it. Since each CreditCard has a reference back to one Customer and each Customer references one CreditCard, we have a one-to-one bidirectional relationship. Relational database schemaThe CreditCard has a corresponding CREDIT_CARD table, so we need to add a CREDIT_CARD foreign key to the CUSTOMER table:
CREATE TABLE CREDIT_CARD
(
ID INT PRIMARY KEY NOT NULL,
EXP_DATE DATE,
NUMBER CHAR(20),
NAME CHAR(40),
ORGANIZATION CHAR(20),
)
CREATE TABLE CUSTOMER
(
ID INT PRIMARY KEY NOT NULL,
LAST_NAME CHAR(20),
FIRST_NAME CHAR(20),
ADDRESS_ID INT,
CREDIT_CARD_ID INT
)
One-to-one bidirectional relationships may model relational database schemas in the same way as our one-to-one unidirectional relationship, in which one of the tables holds a foreign key that references the other. Remember that in a relational database model, there is no such notion of directionality, so the same database schema will be used for both unidirectional and bidirectional object relationships. Figure illustrates how this schema would be implemented for rows in the CUSTOMER and CREDIT_CARD tables. One-to-one bidirectional relationship in RDBMS![]() To model the relationship between the Customer and CreditCard entities, we need to declare a relationship property named customer in the CreditCard bean class:
@Entity
public class CreditCard implements java.io.Serializable {
private int id;
private Date expiration;
private String number;
private String name;
private String organization;
private Customer customer;
...
@OneToOne(mappedBy="creditCard")
public Customer getCustomer( ) {
return this.customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
...
}
The mappedBy( ) attribute is new here. This attribute sets up the bidirectional relationship and tells the persistence manager that the information for mapping this relationship to our tables is specified in the Customer bean class, specifically to the creditCard property of Customer. We also need to add a relationship property to the Customer bean class for the CreditCard relationship:
@Entity
public class Customer implements java.io.Serializable {
private CreditCard creditCard;
...
@OneToOne
(cascade={CascadeType.ALL})
@JoinColumn(name="CREDIT_CARD_ID")
public CreditCard getCreditCard( ) {
return creditCard;
public void setCreditCard(CreditCard card) {
this.creditCard = card;
}
...
}
Here is an example for setting up a bidirectional relationship: Customer cust = new Customer( ); CreditCard card = new CreditCard( ); cust.setCreditCard(card); card.setCustomer(cust); entityManager.persist(cust); We have the cascade( ) attribute set to ALL. When we discuss cascading operations, you will see that this attribute setting causes the CreditCard creation to be cascaded when the Customer entity is persisted. There are some peculiarities with bidirectional relationships. With all bidirectional relationship types, including one-to-one, there is always the concept of an owning side of the relationship. Although a setCustomer( ) method is available in the CreditCard bean class, it will not cause a change in the persistent relationship if we set it. When we marked the @OneToOne relationship in the CreditCard bean class with the mappedBy( ) attribute, this designated the CreditCard entity as the inverse side of the relationship. This means that the Customer entity is the owning side of the relationship. If you wanted to associate a CreditCard instance with a different Customer, you would have to call setCreditCard( ) on the old Customer, passing in null, and then call setCreditCard( ) on the new Customer: Customer newCust = em.find(Customer.class, newCustId); CreditCard card = oldCustomer.getCreditCard( ); oldCustomer.setCreditCard(null); newCust.setCreditCard(card);
If the customer cancelled his credit card, then you would have to set the Customer's creditCard property to null and remove the CreditCard entity from the database: Customer cust = em.find(Customer.class, id); em.remove(cust.getCreditCard( )); cust.setCreditCard(null); Since entity beans are POJOs, it is up to the application developer to manage the relationship, not the persistence provider. This is especially critical when the application deals with detached objects. One-to-one bidirectional XML mappingLet's look at the XML for wiring the Customer/CreditCard entity one-to-one bidirectional relationship:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-one name="creditCard"
target-entity="com.titan.domain.CreditCard"
fetch="LAZY">
<cascade-all/>
<join-column name="CREDIT_CARD_ID"/>
</one-to-one>
</attributes>
</entity>
<entity class="com.titan.domain.CreditCard" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-one name="customer"
target-entity="com.titan.domain.Customer"
mapped-by="creditCard"/>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingAs we saw previously, you do not need to specify metadata like @JoinColumn if your persistence provider supports auto schema generation:
package com.titan.domain;
@Entity
public class Customer implements java.io.Serializable {
...
private CreditCard creditCard;
...
@OneToOne
public CreditCard getCreditCard( ) {
return homeAddress;
}
...
}
@Entity
public class CreditCard implements java.io.Serializable {
...
private Customer customer;
...
@OneToOne(mappedBy="creditCard")
public Customer getCustomer( ) {
return this.customer;
}
...
}
When you do not specify any database mapping for a bidirectional one-to-one relationship, the persistence provider will generate the necessary foreign-key mappings for you. In our Customer/CreditCard relationship example, the following tables would be generated:
CREATE TABLE CUSTOMER
(
ID INT PRIMARY KEY NOT NULL,
creditCard_id INT,
...
)
ALTER TABLE CUSTOMER ADD CONSTRAINT customerREFcreditcard
FOREIGN KEY (creditCard_id) REFERENCES CREDITCARD (id);
For bidirectional one-to-one relationships, the default mapping creates a foreign-key column named from a combination of the property you are mapping followed by an _ character concatenated with the primary-key column name of the referenced table. One-to-Many Unidirectional RelationshipEntity beans can also maintain relationships with multiplicity. This means one entity bean can aggregate or contain many other entity beans. For example, a customer may have relationships with many phones, each of which represents a phone number. This is very different from simple one-to-one relationshipsor, for that matter, from multiple one-to-one relationships with the same type of bean. One-to-many and many-to-many relationships require the developer to work with a collection of references instead of a single reference when accessing the relationship field. Relational database schemaTo illustrate a one-to-many unidirectional relationship, we will use a new entity bean, the Phone, for which we must define a table, the PHONE table:
CREATE TABLE PHONE
(
ID INT PRIMARY KEY NOT NULL,
NUMBER CHAR(20),
TYPE INT,
CUSTOMER_ID INT
)
One-to-many unidirectional relationships between the CUSTOMER and PHONE tables could be implemented in a variety of ways. For this example, we chose to have the PHONE table include a foreign key to the CUSTOMER table. In practice, a unidirectional one-to-many relationship is usually mapped with a join table. The table of aggregated data can maintain a column of nonunique foreign keys to the aggregating table. In the case of the Customer and Phone entities, the PHONE table maintains a foreign key to the CUSTOMER table, and one or more PHONE records may contain foreign keys to the same CUSTOMER record. In other words, in the database, the PHONE records point to the CUSTOMER records. In the programming model, however, it is the Customer entity that points to many Phonestwo schemas are reversed. How does this work? The container system hides the reverse pointer so that it appears as if the Customer is aware of the Phone, and not the other way around. When you ask the container to return a Collection of Phones (invoking a method on the collection returned from getPhoneNumbers( )), it queries the PHONE table for all the records with a foreign key matching the Customer entity's primary key. The use of reverse pointers in this type of relationship is illustrated in Figure. One-to-many unidirectional relationship in RDBMS using a foreign key![]() This database schema illustrates that the structure and relationships of the actual database can differ from the relationships as defined in the programming model. In this case, the tables are set up in reverse, but the persistence manager will manage the beans to meet the specification of the bean developer. When you are dealing with legacy databases (i.e., databases that were established before the EJB application), reverse-pointer scenarios like the one illustrated here are common, so supporting this kind of relationship mapping is important. Programming modelYou declare one-to-many relationships using the @javax.persistence.OneToMany annotation:
public @interface OneToMany
{
Class targetEntity( ) default void.class;
CascadeType[] cascade( ) default {};
FetchType fetch( ) default LAZY;
String mappedBy( ) default "";
}
The attribute definitions are pretty much the same as those for the @OneToOne annotation. In the programming model, we represent multiplicity by defining a relationship property that can point to many entity beans and annotating it with @OneToMany . To hold this type of data, we'll employ some data structures from the java.util package: Collection, List, Map, and Set. The Collection maintains a homogeneous group of entity object references, which means that it contains many references to one kind of entity bean. The Collection type may contain duplicate references to the same entity bean, and the Set type may not. For example, a customer may have relationships with several phone numbers (e.g., a home phone, work phone, cell phone, fax, etc.), each represented by a Phone entity. Instead of having a different relationship field for each Phone, the Customer entity keeps all the Phones in a collection-based relationship:
@Entity
public class Customer implements java.io.Serializable {
...
private Collection<Phone> phoneNumbers = new ArrayList<Phone>( );
...
@OneToMany(cascade={CascadeType.ALL})
@JoinColumn
(name="CUSTOMER_ID")
public Collection<Phone> getPhoneNumbers( ) {
return phoneNumbers;
}
public void setPhoneNumbers(Collection<Phone> phones) {
this.phoneNumbers = phones;
}
}
The @JoinColumn annotation references the CUSTOMER_ID column in the PHONE table. Notice also that we use a Java Generic to templatize the definition of the collection of Phones. Using a Generic is not only good programming practice because it gives a concrete type to your collection, but it also allows the persistence manager to figure out exactly what you are relating the Customer entity to. If you did not use a Generic Collection, then you would have to specify the @OneToMany.targetEntity( ) attribute. Also, since this is a unidirectional relationship, the mappedBy( ) attribute of the @OneToMany annotation did not need to be set. The mappedBy( ) is only used with bidirectional relationships. The Phone bean class is shown in the next listing. Notice that the Phone doesn't provide a relationship property for the Customer. It's a unidirectional relationship; the Customer entity maintains a relationship with many Phones, but the Phones do not maintain a relationship field back to the Customer. Only the Customer is aware of the relationship.
package com.titan.domain;
import javax.persistence.*;
@Entity
public class Phone implements java.io.Serializable {
private int id ;
private String number;
private int type;
// required default constructor
public Phone( ) {}
public Phone(String number, int type) {
this.number = number;
this.type = type;
}
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
public String getNumber( ) { return number; }
public void setNumber(String number) { this.number = number; }
public int getType( ) { return type; }
public void setType(int type) { this.type = type; }
}
To illustrate how an entity bean uses a collection-based relationship, let's look at some code that interacts with the EntityManager:
Customer cust = entityManager.find(Customer.class, pk);
Phone phone = new Phone("617-333-3333", 5);
cust.getPhones( ).add(phone);
Since the Customer entity is the owning side of the relationship, the new Phone will automatically be created in the database because the persistence manager will see that its primary key is 0, generate a new ID (GeneratorType is AUTO ), and insert it into the database. If you need to remove a Phone from the relationship, you need to remove the Phone from both the collection and the database: cust.getPhones( ).remove(phone); entityManager.remove(phone); Removing the Phone from the Customer's collection does not remove the Phone from the database. You have to delete the Phone explicitly; otherwise, it will be orphaned. One-to-many unidirectional XML mappingLet's look at the XML mapping for this type of relationship:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-many name="phones"
targetEntity="com.titan.domain.Phone">
<cascade-all/>
<join-column name="CUSTOMER_ID"/>
</one-to-many>
</attributes>
</entity>
</entity-mappings>
Join table mappingAnother relational database mapping for the Customer/Phone entity relationship could be an association table that maintains two columns with foreign keys pointing to both the CUSTOMER and PHONE records. We could then place a constraint on the PHONE foreign-key column in the association table to ensure that it contains only unique entries (i.e., that every phone has only one customer), while allowing the CUSTOMER foreign-key column to contain duplicates. The advantage of this association table is that it doesn't impose the relationship between the CUSTOMER and PHONE records onto either of the tables. This is the most commonly used mapping when using a unidirectional relationship. create table CUSTOMER_PHONE ( CUSTOMER_ID int not null, PHONE_ID int not null unique ); To have this type of mapping, we need to change from a @JoinColumn annotation in our Customer bean class to using a @javax.persistence.JoinTable annotation:
public @interface JoinTable
{
String name( ) default "";
String catalog( ) default "";
String schema( ) default "";
JoinColumn[] joinColumns( ) default {};
JoinColumn[] inverseJoinColumns( ) default {};
UniqueConstraint[] uniqueConstraints( ) default {};
}
The @JoinTable annotation looks pretty much the same as @Table , except it has the additional attributes of joinColumns( ) and inverseJoinColumns( ). The joinColumns( ) attribute should define a foreign key mapping to the primary key of the owning side of the relationship. The inverseJoinColumns( ) attribute maps the nonowning side. If either side of the relationship had a composite primary key, we would just add more @JoinColumn annotations to the array:
@Entity
public class Customer implements java.io.Serializable {
...
private Collection<Phone> phoneNumbers;
...
@OneToMany(cascade={CascadeType.ALL})
@JoinTable(name="CUSTOMER_PHONE"),
joinColumns={@JoinColumn(name="CUSTOMER_ID")},
inverseJoinColumns={@JoinColumn(name="PHONE_ID")})
public Collection<Phone> getPhoneNumbers( ) {
return phoneNumbers;
}
public void setPhoneNumbers(Collection<Phone> phones) {
this.phoneNumbers = phones;
}
}
With this definition, we're saying that the primary key for Customer maps to the CUSTOMER_ID join column in the CUSTOMER_PHONE table. The primary key of the Phone entity maps to the PHONE_ID join column in the CUSTOMER_PHONE table. Because the relationship between customers and phones is one-to-many, a unique constraint will be put on the PHONE_ID column of the CUSTOMER_PHONE table by the persistence provider if it supports and if you've activated DDL generation. As per the definition of the relationship, one customer has many phones, but a phone has only one customer. The unique constraint enforces this. One-to-many unidirectional join table XML mappingLet's look at the XML for this type of mapping:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-many name="phones" targetEntity="com.titan.domain.Phone">
<cascade-all/>
<join-table name="CUSTOMER_PHONE">
<join-column name="CUSTOMER_ID"/>
<inverse-join-column name="PHONE_ID"/>
</join-table>
</one-to-many>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingIf your persistence provider supports auto schema generation, you do not need to specify metadata like @JoinColumn. Auto schema generation is great when you are doing fast prototypes:
package com.titan.domain;
@Entity
public class Customer implements java.io.Serializable {
...
private Collection<Phone> phoneNumbers = new ArrayList<Phone>( );
...
@OneToMany
public Collection<Phone> getPhoneNumbers( ) {
return phoneNumbers;
}
...
}
When you do not specify any database mapping for a unidirectional one-to-many relationship, the persistence provider does a default mapping based on the join table mapping discussed earlier in this section. For our Customer/Phone relationship example, the following join table would be generated:
CREATE TABLE CUSTOMER_PHONE
(
CUSTOMER_id INT,
PHONE_id INT
);
ALTER TABLE CUSTOMER_PHONE ADD CONSTRAINT customer_phone_unique
UNIQUE (PHONE_id);
ALTER TABLE CUSTOMER_PHONE ADD CONSTRAINT customerREFphone
FOREIGN KEY (CUSTOMER_id) REFERENCES CUSTOMER (id);
ALTER TABLE CUSTOMER_PHONE ADD CONSTRAINT customerREFphone2
FOREIGN KEY (PHONE_id) REFERENCES PHONE (id);
The name of the join table created is a concatenation of the owning entity's table followed by an _ character followed by the table name of the related entity. The foreign-key columns are a concatenation of each entity's table name followed by an _ followed by the primary-key column name of that entity. A unique constraint is placed on the many side of the relationship. Foreign-key constraints are applied to both columns. The Cruise, Ship, and Reservation EntitiesBy now, I imagine that you're bored by all of these phone numbers, credit cards, and addresses. To make things more interesting, we are going to introduce some more entity beans so that we can model the remaining four relationships: many-to-one unidirectional; one-to-many bidirectional; many-to-many bidirectional; and many-to-many unidirectional. In Titan's reservation system, every customer (a.k.a. passenger) can be booked on one or more cruises. Each booking requires a reservation. A reservation may be for one or more (usually two) passengers. Each cruise requires exactly one ship, but each ship may be used for many cruises throughout the year. Figure illustrates these relationships. Cruise, Ship, Reservation, Cabin, and Customer class diagram![]() Many-to-One Unidirectional RelationshipMany-to-one unidirectional relationships result when many entity beans reference a single entity bean, but the referenced entity bean is unaware of the relationship. In the Titan Cruises business, for example, the concept of a cruise can be captured by a Cruise entity bean. As shown in Figure, each Cruise has a many-to-one relationship with a Ship. This relationship is unidirectional: the Cruise entity maintains a relationship with the Ship entity, but the Ship entity does not keep track of the Cruises for which it is used. Relational database schemaThe relational database schema for the Cruise/Ship entity relationship is fairly simple; it requires that the CRUISE table maintain a foreign key column for the SHIP table, with each row in the CRUISE table pointing to a row in the SHIP table. The CRUISE and SHIP tables are defined in the following code snippets; Figure shows the relationship between these tables in the database. Many-to-one unidirectional relationship in RDBMS![]() An enormous amount of data would be required to describe an ocean liner adequately, but we'll use a simple definition of the SHIP table here:
CREATE TABLE SHIP
(
ID INT PRIMARY KEY NOT NULL,
NAME CHAR(30),
TONNAGE DECIMAL (8,2)
)
The CRUISE table maintains data on each cruise's name, ship, and other information that is not germane to this discussion. (Other tables, such as RESERVATIONS, SCHEDULES , and CREW, would have relationships with the CRUISE table through association tables.) We'll keep it simple and focus on a definition that is useful for the examples in this book:
CREATE TABLE CRUISE
(
ID INT PRIMARY KEY NOT NULL,
NAME CHAR(30),
SHIP_ID INT
)
Programming modelMany-to-one relationships are described with the @javax.persistence.ManyToOne annotation:
public @interface ManyToOne
{
Class targetEntity( ) default void.class;
CascadeType[] cascade( ) default {};
FetchType fetch( ) default EAGER;
boolean optional( ) default true;
}
The attribute definitions are pretty much the same as those for the @OneToOne annotation. The programming model is quite simple for our relationship. We add a Ship property to our Cruise entity bean class and annotate it with the @ManyToOne annotation:
@Entity
public class Cruise implements java.io.Serializable {
private int id;
private String name;
private Ship ship;
// required default constructor
public Cruise( ) {}
public Cruise(String name, Ship ship) {
this.name = name;
this.ship = ship;
}
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
public String getName( ) { return name; }
public void setName(String name) { this.name = name; }
@ManyToOne
@JoinColumn
(name="SHIP_ID")
public Ship getShip( ) { return ship; }
public void setShip(Ship ship) { this.ship = ship; }
}
Even though we have a convenience constructor that takes a name and a ship, the Java Persistence spec still requires a default no-arg constructor. The @JoinColumn annotation specifies that the Cruise entity's table has an additional column called SHIP_ID that is a foreign key to the Ship entity's table. If you are using your persistence provider's auto schema generation facilities, you do not need to specify a @JoinColumn as the provider has well-known defaults for this. The relationship between the Cruise and Ship entities is unidirectional, so the Ship bean class doesn't define any relationship back to the Cruise, just persistent properties:
@Entity
public class Ship implements java.io.Serializable {
private int id;
private String name;
private double tonnage;
// required default constructor
public Ship( ) {}
public Ship(String name,double tonnage) {
this.name = name;
this.tonnage = tonnage;
}
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
public String getName( ) { return name; }
public void setName(String name) { this.name = name; }
public double getTonnage( ) { return tonnage ; }
public void setTonnage(double tonnage) { this.tonnage = tonnage ; }
}
All of this should be mundane to you now. The impact of exchanging Ship references between Cruise entities should be equally obvious. As shown previously in Figure, each Cruise may reference only a single Ship, but each Ship may reference many Cruise entities. If you take Ship A, which is referenced by Cruises 1, 2, and 3, and pass it to Cruise 4, Cruises 1 through 4 will reference Ship A, as shown in Figure. Sharing a bean reference in a many-to-one unidirectional relationship![]() Many-to-one unidirectional XML mappingLet's look at the XML mapping for this type of relationship:
<entity-mappings>
<entity class="com.titan.domain.Cruise" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<many-to-one name="ship"
target-entity="com.titan.domain.Ship"
fetch="EAGER">
<join-column name="SHIP_ID"/>
</many-to-one>
</attributes>
</entity>
<entity class="com.titan.domain.Ship" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingIf your persistence provider supports auto schema generation, you do not need to specify metadata like @JoinColumn . Applications can be built very quickly in this manner:
@Entity
public class Cruise implements java.io.Serializable {
...
@ManyToOne
public Ship getShip( ) { return ship; }
The default database mapping for a many-to-one relationship is similar to a unidirectional one-to-one. When you do not specify any database mapping, the persistence provider will generate the necessary foreign-key mappings for you. In our Cruise/Ship relationship example, the following tables would be generated:
CREATE TABLE CRUISE
(
ID INT PRIMARY KEY NOT NULL,
ship_id INT,
...
)
ALTER TABLE CRUISE ADD CONSTRAINT cruiseREFship
FOREIGN KEY (ship_id) REFERENCES SHIP (id);
For unidirectional many-to-one relationships, the default mapping creates a foreign-key column named from a combination of the property you are mapping followed by an _ character concatenated with the primary-key column name of the referenced table. One-to-Many Bidirectional RelationshipOne-to-many and many-to-one bidirectional relationships sound like they're different, but they're not. A one-to-many bidirectional relationship occurs when one entity bean maintains a collection-based relationship property with another entity bean, and each entity bean referenced in the collection maintains a single reference back to its aggregating bean. For example, in the Titan Cruises system, each Cruise entity maintains a collection of references to all the passenger reservations made for that cruise, and each Reservation maintains a single reference to its Cruise. The relationship is a one-to-many bidirectional relationship from the perspective of the Cruise and a many-to-one bidirectional relationship from the perspective of the Reservation. Relational database schemaThe first table we need is the RESERVATION table, which is defined in the following listing. Notice that the RESERVATION table contains, among other things, a column that serves as a foreign key to the CRUISE table.
CREATE TABLE RESERVATION
(
ID INT PRIMARY KEY NOT NULL,
AMOUNT_PAID DECIMAL (8,2),
DATE_RESERVED DATE,
CRUISE_ID INT
)
While the RESERVATION table contains a foreign key to the CRUISE table, the CRUISE table doesn't maintain a foreign key back to the RESERVATION table. The persistence manager can determine the relationship between the Cruise and Reservation entities by querying the RESERVATION table, so explicit pointers from the CRUISE table to the RESERVATION table are not required. This illustrates the separation between the entity bean's view of its persistence relationships and the database's actual implementation of those relationships. The relationship between the RESERVATION and CRUISE tables is shown in Figure. One-to-many/many-to-one bidirectional relationship in RDBMS![]() Programming modelTo model the relationship between Cruises and Reservation entities, we first define the Reservation, which maintains a relationship field to the Cruise:
@Entity
public class Reservation implements java.io.Serializable {
private int id;
private float amountPaid;
private Date date;
private Cruise cruise;
public Reservation( ) {}
public Reservation(Cruise cruise) {
this.cruise = cruise;
}
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
@Column(name="AMOUNT_PAID")
public float getAmountPaid( ) { return amountPaid; }
public void setAmountPaid(float amount) { amountPaid = amount; }
@Column(name="DATE_RESERVED")
public Date getDate( ) { return date; }
public void setDate(Date date) { this.date = date; }
@ManyToOne
@JoinColumn(name="CRUISE_ID")
public Cruise getCruise( ) { return cruise; }
public void setCruise(Cruise cruise) { this.cruise = cruise ; }
}
We need to add a collection-based relationship property to the Cruise bean class so that it can reference all the Reservations that were created for it:
@Entity
public class Cruise implements java.io.Serializable {
...
private Collection<Reservation> reservations = new ArrayList<Reservation>( );
...
@OneToMany(mappedBy="cruise")
public Collection<Reservation> getReservations( ) { return reservations; }
public void setReservations(Collection<Reserveration> res) {
this.reservations = res;
}
}
The interdependency between the Cruise and Reservation entities produces some interesting results. As with one-to-one bidirectional relationships, there must be one owning side of a relationship in a one-to-many bidirectional relationship. Java Persistence currently requires that the many-to-one side always be the ownerin this case, it is the Reservation entity. What does this mean? Well, it requires you to call Reservation.setCruise( ) whenever you add or remove a Cruise's Reservations. If you do not call Reservation.setCruise( ), the relationship will not change in the database. This may seem very confusing, but if you obey the cardinal rule of always wiring both sides of a relationship, then you will have no problems.
In our Titan Cruises system, Reservation entities never change their Cruises once the relationship is set up. If a customer wants to book a different Cruise, he needs to delete the old reservation and create a new one. So, instead of setting Reservation.setCruise( ) to null, application code would just remove the Reservation: entityManager.remove(reservation); Since the Reservation entity is the owning side of the relationship, the Cruise's reservations property is updated with the removal the next time it is loaded from the database. One-to-many bidirectional XML mappingLet's look at the XML mapping for this type of relationship:
<entity-mappings>
<entity class="com.titan.domain.Cruise" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<one-to-many name="ship"
target-entity="com.titan.domain.Reservation"
fetch="LAZY"
mapped-by="cruise">
</one-to-many>
</attributes>
</entity>
<entity class="com.titan.domain.Reservation" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<many-to-one name="cruise"
target-entity="com.titan.domain.Cruise"
fetch="EAGER">
<join-column name="CRUISE_ID"/>
</many-to-one>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingLike other relationship mapping types, if your persistence provider supports auto schema generation, you do not need to specify metadata like @JoinColumn :
@Entity
public class Reservation implements java.io.Serializable {
...
@ManyToOne
public Cruise getCruise( ) { return cruise; }
The default database mapping for a one-to-many bidirectional relationship is similar to a unidirectional many-to-one. When you do not specify any database mapping, the persistence provider will generate the necessary foreign-key mappings for you. In our Reservation/Cruise relationship example, the following tables would be generated:
CREATE TABLE RESERVATION
(
ID INT PRIMARY KEY NOT NULL,
cruise_id INT,
...
)
ALTER TABLE RESERVATION ADD CONSTRAINT reservationREFcruise
FOREIGN KEY (cruise_id) REFERENCES CRUISE (id);
For bidirectional one-to-many relationships, the default mapping creates a foreign-key column named from a combination of the property you are mapping followed by an _ character concatenated with the primary-key column name of the referenced table. Many-to-Many Bidirectional RelationshipMany-to-many bidirectional relationships occur when many beans maintain a collection-based relationship property with another bean, and each bean referenced in the collection maintains a collection-based relationship property back to the aggregating beans. For example, in Titan Cruises, every Reservation entity may reference many Customers (a family can make a single reservation), and each Customer can have many reservations (a person may make more than one reservation). In this many-to-many bidirectional relationship, the Customer keeps track of all of its reservations, and each reservation may be for many customers. Relational database schemaThe RESERVATION and CUSTOMER tables have already been established. To establish a many-to-many bidirectional relationship, we create the RESERVATION_CUSTOMER table. This table maintains two foreign key columns: one for the RESERVATION table and another for the CUSTOMER table:
CREATE TABLE RESERVATION_CUSTOMER
(
RESERVATION_ID INT,
CUSTOMER_ID INT
)
The relationship between the CUSTOMER, RESERVATION, and RESERVATION_CUSTOMER tables is illustrated in Figure. Many-to-many bidirectional relationship in RDBMS![]() Many-to-many bidirectional relationships always require an association table in a normalized relational database. Programming modelMany-to-many relationships are logically defined using the @javax.persistence.ManyToMany annotation:
public @interface ManyToMany
{
Class targetEntity( ) default void.class;
CascadeType[] cascade( ) default {};
FetchType fetch( ) default LAZY;
String mappedBy( ) default "";
}
To model the many-to-many bidirectional relationship between the Customer and Reservation entities, we need to include collection-based relationship properties in both bean classes:
@Entity
public class Reservation implements java.io.Serializable {
...
private Set<Customer> customers = new HashSet<Customer>( );
...
@ManyToMany
@JoinTable
(name="RESERVATION_CUSTOMER"),
joinColumns={@JoinColumn(name="RESERVATION_ID")},
inverseJoinColumns={@JoinColumn(name="CUSTOMER_ID")})
public Set<Customer> getCustomers( ) { return customers; }
public void setCustomers(Set customers);
...
}
The customers relationship is declared as a java.util.Set . The Set type should contain only unique Customers and no duplicates. Duplicate Customers would introduce some interesting but undesirable side effects in Titan's reservation system. To maintain a valid passenger count, and to avoid overcharging customers, Titan requires that a customer be booked only once in the same reservation. The Set collection type expresses this restriction. The effectiveness of the Set collection type depends largely on referential-integrity constraints established in the underlying database. As with all bidirectional relationships, there has to be an owning side. In this case, it is the Reservation entity. Since the Reservation owns the relationship, its bean class defines the @JoinTable mapping. The joinColumns( ) attribute identifies the foreign-key column in the RESERVATION_CUSTOMER table that references the RESERVATION table. The inverseJoinColumns( ) attribute identifies the foreign key in the RESERVATION_CUSTOMER table that references the CUSTOMER table. Like with @OneToMany relationships, if you are using your persistence provider's auto schema generation facilities, you do not need to specify a @JoinTable mapping. The Java Persistence specification has a default mapping for @ManyToMany relationships and will create the join table for you. We have also modified the Customer to allow it to maintain a collection-based relationship with all of its Reservations. The Customer bean class now includes a reservations relationship property:
@Entity
public class Customer implements java.io.Serializable {
...
private Collection<Reservation> reservations = new ArrayList<Reservation>( );
...
@ManyToMany(mappedBy="customers")
public Collection<Reservation> getReservations( ) {
return reservations;
}
public void setReservations(Collection<Reservation> reservations) {
this.reservations = reservations;
}
...
As with one-to-many bidirectional relationships, the mappedBy( ) attribute identifies the property on the Reservation bean class that defines the relationship. This also identifies the Customer entity as the inverse side of the relationship. As far as modifying and interacting with the relationship properties, the same ownership rules apply as we saw in the one-to-many bidirectional example. The Customer/Reservation entity relationship is something our Titan Cruises application may want to modify after it is established. The customer may want to add a relative or nanny to the reservation, or remove a friend that is too sick to come on the cruise: Reservation reservation = em.find(Reservation.class, id); reservation.getCustomers( ).remove(customer); Since the reservation is the owning side of the relationship, you must remove the customer from the Reservation's customers property. If you instead removed the reservation from the Customer's reservations property, there would be no database update because the Customer entity is the inverse side of the relationship. Many-to-many bidirectional XML mappingLet'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">
<join-table name="RESERVATION_CUSTOMER">
<join-column name="RESERVATION_ID"/>
<inverse-join-column name="CUSTOMER_ID"/>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<many-to-many name="cruise"
target-entity="com.titan.domain.Reservation"
fetch="LAZY"
mapped-by="customers">
</many-to-many>
</attributes>
</entity>
</entity-mappings>
Default Rrelationship mappingLike the other relationship types, bidirectional many-to-many relationships support auto schema generation with minimal metadata:
public class Reservation implements java.io.Serializable {
...
private Set<Customer> customers = new HashSet<Customer>( );
...
@ManyToMany
public Set<Customer> getCustomers( ) { return customers; }
public void setCustomers(Set customers);
...
}
When you do not specify any database mapping for a bidirectional many-to-many relationship, the persistence provider creates the join table mapping for you. For our Reservation/Customer relationship example, the following join table would be generated:
CREATE TABLE RESERVATION_CUSTOMER
(
RESERVATION_id INT,
CUSTOMER_id INT,
);
ALTER TABLE RESERVATION_CUSTOMER ADD CONSTRAINT reservationREFcustomer
FOREIGN KEY (RESERVATION_id) REFERENCES RESERVATION (id);
ALTER TABLE RESERVATION_CUSTOMER ADD CONSTRAINT reservationREFcustomer2
FOREIGN KEY (CUSTOMER_id) REFERENCES CUSTOMER (id);
The name of the join table created is a concatenation of the owning entity's table followed by an _ character followed by the table name of the related entity. The foreign-key columns are a concatenation of each entity's table name followed by an _ followed by the primary-key column name of that entity. Foreign-key constraints are applied to both columns. Many-to-Many Unidirectional RelationshipMany-to-many unidirectional relationships occur when many beans maintain a collection-based relationship with another bean, but the bean referenced in the Collection does not maintain a collection-based relationship back to the aggregating beans. In Titan's reservation system, every Reservation is assigned a Cabin on the Ship. This allows a customer to reserve a specific cabin (e.g., a deluxe suite or a cabin with sentimental significance) on the ship. In this case, each reservation may be for more than one cabin, since each reservation can be for more than one customer. For example, a family might make a reservation for five people for two adjacent cabins (one for the kids and the other for the parents). While the Reservation entity must keep track of the Cabins it reserves, it's not necessary for the Cabins to track all the Reservations made by all the Cruises. The Reservations reference a collection of Cabin beans, but the Cabin beans do not maintain references back to the Reservations. Relational database schemaOur first order of business is to declare a CABIN table:
CREATE TABLE CABIN
(
ID INT PRIMARY KEY NOT NULL,
SHIP_ID INT,
NAME CHAR(10),
DECK_LEVEL INT,
BED_COUNT INT
)
The CABIN table maintains a foreign key to the SHIP table. While this relationship is important, we don't discuss it because we covered the one-to-many bidirectional relationship earlier in this chapter. To accommodate the many-to-many unidirectional relationship between the RESERVATION and CABIN tables, we need a CABIN_RESERVATION table:
CREATE TABLE CABIN_RESERVATION
(
RESERVATION_ID INT,
CABIN_ID INT
)
The relationship between the CABIN records and the RESERVATION records through the CABIN_RESERVATION table is illustrated in Figure. Many-to-many unidirectional relationship in RDBMS![]() This many-to-many unidirectional relationship looks a lot like the join table mapping for the one-to-many unidirectional Customer/Phone relationship discussed earlier. The big difference is that there are no unique constraints on the CABIN_RESERVATION table: many cabins can have many reservations, and vice versa. Programming modelTo model this relationship, we need to add a collection-based relationship field for Cabin beans to the Reservation:
@Entity
public class Reservation implements java.io.Serializable {
...
@ManyToMany
@JoinTable(name="CABIN_RESERVATION",
joinColumns={@JoinColumn(name="RESERVATION_ID")},
inverseJoinColumns={@JoinColumn(name="CABIN_ID")})
public Set<Cabin> getCabins( ) { return cabins; }
public void setCabins(Set<Cabin> cabins) { this.cabins = cabins; }
...
}
In addition, we need to define a Cabin bean. Notice that the Cabin bean doesn't maintain a relationship back to the Reservation. The lack of a relationship field for the Reservation tells us the relationship is unidirectional:
@Entity
public class Cabin implements java.io.Serializable {
private int id;
private String name;
private int bedCount;
private int deckLevel;
private Ship ship;
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
public String getName( ) { return name; }
public void setName(String name) { this.name = name; }
@Column(name="BED_COUNT")
public int getBedCount( ) { return bedCount; }
public void setBedCount(int count) { this.bedCount = count; }
@Column(name="DECK_LEVEL")
public int getDeckLevel( ) { return deckLevel; }
public void setDeckLevel(int level) { this.deckLevel = level; }
@ManyToOne
@JoinColumn(name="SHIP_ID")
public Ship getShip( ) { return ship; }
public void setShip(Ship ship) { this.ship = ship; }
}
Although the Cabin bean doesn't define a relationship field for the Reservation, it does define a one-to-many bidirectional relationship for the Ship. The effect of exchanging relationship fields in a many-to-many unidirectional relationship is the same as in a many-to-many bidirectional relationship. Many-to-many unidirectional XML mappingLet'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="cabins"
target-entity="com.titan.domain.Cabin"
fetch="LAZY">
<join-table name="CABIN_RESERVATION">
<join-column name="RESERVATION_ID"/>
<inverse-join-column name="CABIN_ID"/>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity class="com.titan.domain.Cabin" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<many-to-one name="ship"
target-entity="com.titan.domain.Ship"
fetch="LAZY">
<join-column name="SHIP_ID"/>
</many-to-one>
</attributes>
</entity>
</entity-mappings>
Default relationship mappingIf your persistence provider supports auto schema generation, you do not need to specify metadata like @JoinTable :
@Entity
public class Reservation implements java.io.Serializable {
...
@ManyToMany
public Set<Cabin> getCabins( ) { return cabins; }
public void setCabins(Set<Cabin> cabins) { this.cabins = cabins; }
...
}
When you do not specify any database mapping for a unidirectional many-to-many relationship, the persistence provider creates the join table mapping for you. For our Reservation/Cabin relationship example, the following join table would be generated:
CREATE TABLE RESERVATION_CABIN
(
RESERVATION_id INT,
CABIN_id INT,
);
ALTER TABLE RESERVATION_CABIN ADD CONSTRAINT reservationREFcabin
FOREIGN KEY (RESERVATION_id) REFERENCES RESERVATION (id);
ALTER TABLE RESERVATION_CABIN ADD CONSTRAINT reservationREFcabin2
FOREIGN KEY (CABIN_id) REFERENCES CABIN (id);
The name of the join table created is a concatenation of the owning entity's table followed by an _ character followed by the table name of the related entity. The foreign-key columns are a concatenation of each entity's table name followed by an _ followed by the primary key column name of that entity. Foreign-key constraints are applied to both columns. |
- Comment










