7.1. Single-valued entity associations

Let's start with one-to-one entity associations.

We argued in chapter 4 that the relationships between User and Address (the user has a billingAddress, homeAddress, and shippingAddress) are best represented with a <component> mapping. This is usually the simplest way to represent one-to-one relationships, because the lifecycle is almost always dependent in such a case, it's either an aggregation or a composition in UML.

But what if you want a dedicated table for Address, and you map both User and Address as entities? One benefit of this model is the possibility for shared references—another entity class (let's say Shipment) can also have a reference to a particular Address instance. If a User has a reference to this instance, as their shippingAddress, the Address instance has to support shared references and needs its own identity.

In this case, User and Address classes have a true one-to-one association. Look at the revised class diagram in figure 7.1.

The first change is a mapping of the Address class as a stand-alone entity:

<class name="Address" table="ADDRESS">
    <id name="id" column="ADDRESS_ID">
        <generator .../>
    </id>
    <property name="street" column="STREET"/>

    <property name="city" column="CITY"/>
    <property name="zipcode" column="ZIPCODE"/>
</class>

We assume you won't have any difficulty creating the same mapping with annotations or changing the Java class to an entity, with an identifier property—this is the only change you have to make.

Now let's create the association mappings from other entities to that class. There are several choices, the first being a primary key one-to-one association.

Figure 7-1. Address as an entity with two associations referencing the same instance

7.1.1. Shared primary key associations

Rows in two tables related by a primary key association share the same primary key values. The main difficulty with this approach is ensuring that associated instances are assigned the same primary key value when the objects are saved. Before we try to solve this problem, let's see how you map the primary key association.

Mapping a primary key association with XML

The XML mapping element that maps an entity association to a shared primary key entity is <one-to-one>. First you need a new property in the User class:

public class User {
   ...
   private Address shippingAddress;
   // Getters and setters
}

Next, map the association in User.hbm.xml:

<one-to-one name="shippingAddress"
            class="Address"
            cascade="save-update"/>

You add a cascading option that is natural for this model: If a User instance is made persistent, you usually also want its shippingAddress to become persistent. Hence, the following code is all that is needed to save both objects:

User newUser = new User();
Address shippingAddress = new Address();

newUser.setShippingAddress(shippingAddress);

session.save(newUser);

Hibernate inserts a row into the USERS table and a row into the ADDRESS table. But wait, this doesn't work! How can Hibernate possibly know that the record in the ADDRESS table needs to get the same primary key value as the USERS row? At the beginning of this section, we intentionally didn't show you any primary-key generator in the mapping of Address.

You need to enable a special identifier generator.

The foreign identifier generator

If an Address instance is saved, it needs to get the primary key value of a User object. You can't enable a regular identifier generator, let's say a database sequence. The special foreign identifier generator for Address has to know where to get the right primary key value.

The first step to create this identifier binding between Address and User is a bidirectional association. Add a new user property to the Address entity:

public class Address {
   ...
    private User user;
   // Getters and setters
}

Map the new user property of an Address in Address.hbm.xml:

<one-to-one name="user"
            class="User"
            constrained="true"/>

This mapping not only makes the association bidirectional, but also, with constrained="true", adds a foreign key constraint linking the primary key of the ADDRESS table to the primary key of the USERS table. In other words, the database guarantees that an ADDRESS row's primary key references a valid USERS primary key. (As a side effect, Hibernate can now also enable lazy loading of users when a shipping address is loaded. The foreign key constraint means that a user has to exist for a particular shipping address, so a proxy can be enabled without hitting the database. Without this constraint, Hibernate has to hit the database to find out if there is a user for the address; the proxy would then be redundant. We'll come back to this in later chapters.)

You can now use the special foreign identifier generator for Address objects:

<class name="Address" table="ADDRESS">

    <id name="id" column="ADDRESS_ID">
        <generator class="foreign">
            <param name="property">user</param>
        </generator>
    </id>
    ...
    <one-to-one name="user"
                class="User"
                constrained="true"/>

</class>

This mapping seems strange at first. Read it as follows: When an Address is saved, the primary key value is taken from the user property. The user property is a reference to a User object; hence, the primary key value that is inserted is the same as the primary key value of that instance. Look at the table structure in figure 7.2.

Figure 7-2. The USERS and ADDRESS tables have the same primary keys.

The code to save both objects now has to consider the bidirectional relationship, and it finally works:

User newUser = new User();
Address shippingAddress = new Address();

newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser);            // Bidirectional

session.save(newUser);

Let's do the same with annotations.

Shared primary key with annotations

JPA supports one-to-one entity associations with the @OneToOne annotation. To map the association of shippingAddress in the User class as a shared primary key association, you also need the @PrimaryKeyJoinColumn annotation:

@OneToOne
@PrimaryKeyJoinColumn
private Address shippingAddress;

This is all that is needed to create a unidirectional one-to-one association on a shared primary key. Note that you need @PrimaryKeyJoinColumns (plural) instead if you map with composite primary keys. In a JPA XML descriptor, a one-to-one mapping looks like this:

<entity-mappings>

    <entity class="auction.model.User" access="FIELD">
        ...
        <one-to-one name="shippingAddress">
            <primary-key-join-column/>
        </one-to-one>
    </entity>

</entity-mappings>

The JPA specification doesn't include a standardized method to deal with the problem of shared primary key generation, which means you're responsible for setting the identifier value of an Address instance correctly before you save it (to the identifier value of the linked User instance). Hibernate has an extension annotation for custom identifier generators which you can use with the Address entity (just like in XML):

@Entity
@Table(name = "ADDRESS")
public class Address {

    @Id @GeneratedValue(generator = "myForeignGenerator")
    @org.hibernate.annotations.GenericGenerator(
        name = "myForeignGenerator",
        strategy = "foreign",
        parameters = @Parameter(name = "property", value = "user")
    )
    @Column(name = "ADDRESS_ID")
    private Long id;

    ...
    private User user;
 }

Shared primary key one-to-one associations aren't uncommon but are relatively rare. In many schemas, a to-one association is represented with a foreign key field and a unique constraint.

7.1.2. One-to-one foreign key associations

Instead of sharing a primary key, two rows can have a foreign key relationship. One table has a foreign key column that references the primary key of the associated table. (The source and target of this foreign key constraint can even be the same table: This is called a self-referencing relationship.)

Let's change the mapping from a User to an Address. Instead of the shared primary key, you now add a SHIPPING_ADDRESS_ID column in the USERS table:

<class name="User" table="USERS">

    <many-to-one name="shippingAddress"
                 class="Address"
                 column="SHIPPING_ADDRESS_ID"
                 cascade="save-update"
                 unique="true"/>

</class>

The mapping element in XML for this association is <many-to-one>—not <one-to-one>, as you might have expected. The reason is simple: You don't care what's on the target side of the association, so you can treat it like a to-one association without the many part. All you want is to express "This entity has a property that is a reference to an instance of another entity" and use a foreign key field to represent that relationship. The database schema for this mapping is shown in figure 7.3.

Figure 7-3. A one-to-one foreign key association between USERS and ADDRESS

An additional constraint enforces this relationship as a real one to one. By making the SHIPPING_ADDRESS_ID column unique, you declare that a particular address can be referenced by at most one user, as a shipping address. This isn't as strong as the guarantee from a shared primary key association, which allows a particular address to be referenced by at most one user, period. With several foreign key columns (let's say you also have unique HOME_ADDRESS_ID and BILLING_ADDRESS_ID), you can reference the same address target row several times. But in any case, two users can't share the same address for the same purpose.

Let's make the association from User to Address bidirectional.

Inverse property reference

The last foreign key association was mapped from User to Address with <many-to-one> and a unique constraint to guarantee the desired multiplicity. What mapping element can you add on the Address side to make this association bidirectional, so that access from Address to User is possible in the Java domain model?

In XML, you create a <one-to-one> mapping with a property reference attribute:

<one-to-one name="user"
            class="User"
            property-ref="shippingAddress"/>

You tell Hibernate that the user property of the Address class is the inverse of a property on the other side of the association. You can now call anAddress.getUser() to access the user who's shipping address you've given. There is no additional column or foreign key constraint; Hibernate manages this pointer for you.

Should you make this association bidirectional? As always, the decision is up to you and depends on whether you need to navigate through your objects in that direction in your application code. In this case, we'd probably conclude that the bidirectional association doesn't make much sense. If you call anAddress.getUser(), you are saying "give me the user who has this address has its shipping address," not a very reasonable request. We recommend that a foreign key-based one-to-one association, with a unique constraint on the foreign key column—is almost always best represented without a mapping on the other side.

Let's repeat the same mapping with annotations.

Mapping a foreign key with annotations

The JPA mapping annotations also support a one-to-one relationship between entities based on a foreign key column. The main difference compared to the mappings earlier in this chapter is the use of @JoinColumn instead of @PrimaryKeyJoinColumn.

First, here's the to-one mapping from User to Address with the unique constraint on the SHIPPING_ADDRESS_ID foreign key column. However, instead of a @ManyToOne annotation, this requires a @OneToOne annotation:

public class User {
    ...

    @OneToOne
    @JoinColumn(name="SHIPPING_ADDRESS_ID")
    private Address shippingAddress;
    ...
}

Hibernate will now enforce the multiplicity with the unique constraint. If you want to make this association bidirectional, you need another @OneToOne mapping in the Address class:

public class Address {
    ...

    @OneToOne(mappedBy = "shippingAddress")
    private User user;
    ...

}

The effect of the mappedBy attribute is the same as the property-ref in XML mapping: a simple inverse declaration of an association, naming a property on the target entity side.

The equivalent mapping in JPA XML descriptors is as follows:

<entity-mappings>

    <entity class="auction.model.User" access="FIELD">
        ...
        <one-to-one name="shippingAddress">
            <join-column name="SHIPPING_ADDRESS_ID"/>
        </one-to-one>
    </entity>

    <entity class="auction.model.Address" access="FIELD">
        ...
        <one-to-one name="user" mapped-by="shippingAddress"/>
    </entity>

</entity-mappings>

You've now completed two basic single-ended association mappings: the first with a shared primary key, the second with a foreign key reference. The last option we want to discuss is a bit more exotic: mapping a one-to-one association with the help of an additional table.

7.1.3. Mapping with a join table

Let's take a break from the complex CaveatEmptor model and consider a different scenario. Imagine you have to model a data schema that represents an office allocation plan in a company. Common entities include people working at desks. It seems reasonable that a desk may be vacant and have no person assigned to it. On the other hand, an employee may work at home, with the same result. You're dealing with an optional one-to-one association between Person and Desk.

If you apply the mapping techniques we discussed in the previous sections, you may come to the following conclusions: Person and Desk are mapped to two tables, with one of them (let's say the PERSON table) having a foreign key column that references the other table (such as ASSIGNED_DESK_ID) with an additional unique constraint (so two people can't be assigned the same desk). The relationship is optional if the foreign key column is nullable.

On second thought, you realize that the assignment between persons and desks calls for another table that represents ASSIGNMENT. In the current design, this table has only two columns: PERSON_ID and DESK_ID. The multiplicity of these foreign key columns is enforced with a unique constraint on both—a particular person and desk can only be assigned once, and only one such an assignment can exist.

It also seems likely that one day you'll need to extend this schema and add columns to the ASSIGNMENT table, such as the date when a person was assigned to a desk. As long as this isn't the case, however, you can use object/relational mapping to hide the intermediate table and create a one-to-one Java entity association between only two classes. (This situation changes completely once additional columns are introduced to ASSIGNMENT.)

Where does such an optional one-to-one relationship exist in CaveatEmptor?

The CaveatEmptor use case

Let's consider the Shipment entity in CaveatEmptor again and discuss its purpose. Sellers and buyers interact in CaveatEmptor by starting and bidding on auctions. The shipment of the goods seems to be outside the scope of the application; the seller and the buyer agree on a method of shipment and payment after the auction ends. They can do this offline, outside of CaveatEmptor. On the other hand, you could offer an extra escrow service in CaveatEmptor. Sellers would use this service to create a trackable shipment once the auction completed. The buyer would pay the price of the auction item to a trustee (you), and you'd inform the seller that the money was available. Once the shipment arrived and the buyer accepted it, you'd transfer the money to the seller.

If you've ever participated in an online auction of significant value, you've probably used such an escrow service. But you want more service in CaveatEmptor. Not only will you provide trust services for completed auctions, but you'll also allow users to create a trackable and trusted shipment for any deal they make outside an auction, outside CaveatEmptor.

This scenario calls for a Shipment entity with an optional one-to-one association to an Item. Look at the class diagram for this domain model in figure 7.4.

Figure 7-4. A shipment has an optional link with a single auction item.

In the database schema, you add an intermediate link table called ITEM_SHIPMENT. A row in this table represents a Shipment made in the context of an auction. The tables are shown in figure 7.5.

You now map two classes to three tables: first in XML, and then with annotations.

Mapping a join table in XML

The property that represents the association from Shipment to Item is called auction:

public class Shipment {

    ...
    private Item auction;
    ...
    // Getter/setter methods
}

Because you have to map this association with a foreign key column, you need the <many-to-one> mapping element in XML. However, the foreign key column isn't in the SHIPMENT table, it's in the ITEM_SHIPMENT join table. With the help of the <join> mapping element, you move it there.

Figure 7-5. An optional one-to-many relationship mapped to a join table

<class name="Shipment" table="SHIPMENT">

    <id name="id" column="SHIPMENT_ID">...</id>

    ...

    <join table="ITEM_SHIPMENT" optional="true">
        <key column="SHIPMENT_ID"/>
        <many-to-one name="auction"
                     column="ITEM_ID"
                     not-null="true"
                     unique="true"/>
    </join>

</class>

The join table has two foreign key columns: SHIPMENT_ID, referencing the primary key of the SHIPMENT table; and ITEM_ID, referencing the ITEM table. The ITEM_ID column is unique; a particular item can be assigned to exactly one shipment. Because the primary key of the join table is SHIPMENT_ID, which makes this column also unique, you have a guaranteed one-to-one multiplicity between Shipment and Item.

By setting optional="true" on the <join> mapping, you tell Hibernate that it should insert a row into the join table only if the properties grouped by this mapping are non-null. But if a row needs to be inserted (because you called aShipment.setAuction(anItem)), the NOT NULL constraint on the ITEM_ID column applies.

You could map this association bidirectional, with the same technique on the other side. However, optional one-to-one associations are unidirectional most of the time.

JPA also supports association join tables as secondary tables for an entity.

Mapping secondary join tables with annotations

You can map an optional one-to-one association to an intermediate join table with annotations:

public class Shipment {

    @OneToOne
    @JoinTable(
        name="ITEM_SHIPMENT",
        joinColumns = @JoinColumn(name = "SHIPMENT_ID"),
        inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
    )
    private Item auction;
    ...
    // Getter/setter methods
}

You don't have to specify the SHIPMENT_ID column because it's automatically considered to be the join column; it's the primary key column of the SHIPMENT table.

Alternatively, you can map properties of a JPA entity to more than one table, as demonstrated in "Moving properties into a secondary table" in chapter 8, section 8.1.3. First, you need to declare the secondary table for the entity:

@Entity
@Table(name = "SHIPMENT")
@SecondaryTable(name = "ITEM_SHIPMENT")
public class Shipment {

    @Id @GeneratedValue
    @Column(name = "SHIPMENT_ID")
    private Long id;

    ...
}

Note that the @SecondaryTable annotation also supports attributes to declare the foreign-key column name—the equivalent of the <key column="..."/> you saw earlier in XML and the joinColumn(s) in a @JoinTable. If you don't specify it, the primary-key column name of the entity is used—in this case, again SHIPMENT_ID.

The auction property mapping is a @OneToOne; and as before, the foreign key column referencing the ITEM table is moved to the intermediate secondary table:

...
public class Shipment {
    ...
    @OneToOne
    @JoinColumn(table = "ITEM_SHIPMENT", name = "ITEM_ID")
    private Item auction;
}

The table for the target @JoinColumn is named explicitly. Why would you use this approach instead of the (simpler) @JoinTable strategy? Declaring a secondary table for an entity is useful if not only one property (the many-to-one in this case) but several properties must be moved into the secondary table. We don't have a great example with Shipment and Item, but if your ITEM_SHIPMENT table would have additional columns, mapping these columns to properties of the Shipment entity might be useful.

This completes our discussion of one-to-one association mappings. To summarize, use a shared primary key association if one of the two entities seems more important and can act as the primary key source. Use a foreign key association in all other cases, and a hidden intermediate join table when your one-to-one association is optional.

We now focus on many-valued entity associations, including more options for one-to-many, and finally, many-to-many mappings.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.13.11