• No results found

Polymorphic associations

Mapping inheritance

6.8 Polymorphic associations

Polymorphism is a defining feature of object-oriented languages like Java. Support for polymorphic associations and polymorphic queries is a fundamental feature of an ORM solution like Hibernate. Surprisingly, we’ve managed to get this far without needing to talk much about polymorphism. Refreshingly, there isn’t much to say on the topic—polymorphism is so easy to use in Hibernate that we don’t need to expend a lot of effort explaining it.

To provide an overview, we first consider a many-to-one association to a class that may have subclasses, and then a one-to-many relationship. For both examples, the classes of the domain model are the same; see figure 6.6.

6.8.1 Polymorphic many-to-one associations

First, consider the defaultBilling property of User. It references one particular BillingDetails instance, which at runtime can be any concrete instance of that class. You map this unidirectional association to the abstract class BillingDetails as follows:

@Entity

@Table(name = "USERS")

public class User {

@ManyToOne(fetch = FetchType.LAZY) protected BillingDetails defaultBilling;

// ...

}

The USERS table now has the join/foreign key column DEFAULTBILLING_ID represent- ing this relationship. It’s a nullable column because a User might not have a default billing method assigned. Because BillingDetails is abstract, the association must refer to an instance of one of its subclasses—CreditCard or BankAccount—at runtime. You don’t have to do anything special to enable polymorphic associations in Hiber- nate; if the target class of an association is mapped with @Entity and @Inheritance, the association is naturally polymorphic.

PATH: /model/src/main/java/org/jpwh/model/inheritance/associations/manytoone/ User.java username : String lastname : String User default 0..* owner : String BillingDetails cardNumber : String expMonth: String expYear : String CreditCard account : String bankname : String swift : String BankAccount firstname : String

Figure 6.6 A user has either a credit card or a bank account as the default billing details.

The following code demonstrates the creation of an association to an instance of the CreditCard subclass:

CreditCard cc = new CreditCard(

"John Doe", "1234123412341234", "06", "2015"

);

User johndoe = new User("johndoe"); johndoe.setDefaultBilling(cc); em.persist(cc);

em.persist(johndoe);

Now, when you navigate the association in a second unit of work, Hibernate automati- cally retrieves the CreditCard instance:

User user = em.find(User.class, USER_ID); user.getDefaultBilling().pay(123);

There’s just one thing to watch out for: because the defaultBilling property is mapped with FetchType.LAZY, Hibernate will proxy the association target. In this case, you wouldn’t be able to perform a typecast to the concrete class CreditCard at runtime, and even the instanceof operator would behave strangely:

User user = em.find(User.class, USER_ID);

BillingDetails bd = user.getDefaultBilling(); assertFalse(bd instanceof CreditCard);

// CreditCard creditCard = (CreditCard) bd;

The bd reference isn’t a CreditCard instance in this case; it’s a runtime-generated spe- cial subclass of BillingDetails, a Hibernate proxy. When you invoke a method on the proxy, Hibernate delegates the call to an instance of CreditCard that it fetches lazily. Until this initialization occurs, Hibernate doesn’t know what the subtype of the given instance is—this would require a database hit, which you try to avoid with lazy loading in the first place. To perform a proxy-safe typecast, use em.getReference():

PATH: /examples/src/test/java/org/jpwh/test/inheritance/ PolymorphicManyToOne.java PATH: /examples/src/test/java/org/jpwh/test/inheritance/ PolymorphicManyToOne.java PATH: /examples/src/test/java/org/jpwh/test/inheritance/ PolymorphicManyToOne.java

Invokes pay() method on concrete subclass of BillingDetails

Don’t do this— ClassCastException!

User user = em.find(User.class, USER_ID);

BillingDetails bd = user.getDefaultBilling();

CreditCard creditCard =

em.getReference(CreditCard.class, bd.getId()); assertTrue(bd != creditCard);

After the getReference() call, bd and creditCard refer to two different proxy instances, both of which delegate to the same underlying CreditCard instance. The sec- ond proxy has a different interface, though, and you can call methods like creditCard .getExpMonth() that apply only to this interface. (Note that bd.getId() will trigger a SELECT if you map the id property with field access.)

You can avoid these issues by avoiding lazy fetching, as in the following code, using an eager fetch query:

User user = (User) em.createQuery( "select u from User u " +

"left join fetch u.defaultBilling " + "where u.id = :id")

.setParameter("id", USER_ID) .getSingleResult();

CreditCard creditCard = (CreditCard) user.getDefaultBilling();

Truly object-oriented code shouldn’t use instanceof or numerous typecasts. If you find yourself running into problems with proxies, you should question your design, asking whether there is a more polymorphic approach. Hibernate also offers bytecode instrumentation as an alternative to lazy loading through proxies; we’ll get back to fetching strategies in chapter 12.

You can handle one-to-one associations the same way. What about plural associa- tions, like the collection of billingDetails for each User? Let’s look at that next.

6.8.2 Polymorphic collections

A User may have references to many BillingDetails, not only a single default (one of the many is the default; let’s ignore that for now). You can map this with a bidirec- tional one-to-many association:

PATH: /examples/src/test/java/org/jpwh/test/inheritance/ PolymorphicManyToOne.java PATH: /examples/src/test/java/org/jpwh/test/inheritance/ PolymorphicManyToOne.java No SELECT Careful!

No proxy has been used: BillingDetails instance fetched eagerly

@Entity

@Table(name = "USERS")

public class User {

@OneToMany(mappedBy = "user")

protected Set<BillingDetails> billingDetails = new HashSet<>();

// ...

}

Next, here’s the owning side of the relationship (declared with mappedBy in the previ- ous mapping):

@Entity

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

public abstract class BillingDetails { @ManyToOne(fetch = FetchType.LAZY) protected User user;

// ...

}

So far, there is nothing special about this association mapping. The BillingDetails class hierarchy can be mapped with TABLE_PER_CLASS, SINGLE_TABLE, or a JOINED inheritance type. Hibernate is smart enough to use the right SQL queries, with either JOIN or UNION operators, when loading the collection elements.

There is one limitation, however: the BillingDetails class can’t be a @Mapped- Superclass, as shown in section 6.1. It has to be mapped with @Entity and @Inheritance.

PATH: /model/src/main/java/org/jpwh/model/inheritance/associations/onetomany/ User.java

PATH: /model/src/main/java/org/jpwh/model/inheritance/associations/onetomany/ BillingDetails.java

Associations with implicit polymorphism

Hibernate offers a "last resort" technique, if you really have to map an association to a class hierarchy without mapping the class hierarchy explicitly with @Inheri- tance. This is possible with Hibernate native XML mappings and the <any/> ele- ment. We recommend that you look this one up in the Hibernate documentation or a previous edition of this book if needed, but try to avoid it whenever possible because it results in ugly schemas.

6.9

Summary

 Table per concrete class with implicit polymorphism is the simplest strategy to map inheritance hierarchies of entities, but it doesn’t support polymorphic associations very well. In addition, different columns from different tables share exactly the same semantics, making schema evolution more complex. We rec- ommend this approach for the top level of your class hierarchy only, where polymorphism isn’t usually required and when modification of the superclass in the future is unlikely.

 The table-per-concrete-class-with-unions strategy is optional, and JPA implemen- tations may not support it, but it does handles polymorphic associations.

 The table-per-class-hierarchy strategy is a winner in terms of both performance and simplicity; ad hoc reporting is possible without complex joins or unions, and schema evolution is straightforward. The one major problem is data integ- rity, because you must declare some columns as nullable. Another issue is nor- malization: this strategy creates functional dependencies between non-key columns, violating the third normal form.

 The table-per-subclass-with-joins strategy’s primary advantage is that it normal- izes the SQL schema, making schema evolution and integrity constraint defini- tion straightforward. The disadvantages are that it’s more difficult to implement by hand, and performance can be unacceptable for complex class hierarchies.

140

Mapping collections