Friday, January 26, 2007

Hibernate alternatives for mappedBy to a superclass property

I've been working on a JPA/Hibernate prototype of an application that was previously mapped using Toplink. So this is the "meet in the middle" where there is an existing domain model that must be mapped to an existing schema. In reality, the domain model is somewhat free to change as long as the public interface stays the same. And if push comes to shove, schema changes are possible (but undesirable).

While trying to map a relationship to a superclass, I assumed that O/R relationships can be inherited in a manner analogous to OO. So I naively assumed I could:

  • make the superclass an @Entity

  • use single table inheritance

  • use a @ManyToOne in the superclass (and then do a @OneToMany with a mappedBy= in the other side of the relationship)

  • set up a discriminator column on the superclass

  • provide discriminator values in the subclasses

and viola... other classes could then hold references to the sublclasses.

So the key assumption here is that mappedBy could simply reference the property of the subclass even though the property is actually in the superclass (as you can do in an OO sense). But I ran into problems trying this. Such as: javax.persistence.PersistenceException: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property:. And no, it's not simply a field vs. accessor visibility issue. You can try different variations (including the sin of making it a public reference) and it will have no effect.

So this explores some of the options that were tried, and what the tradeoff's are...

In the examples that follow (the naive and wrong way):

  • ExternalContactAssignment is the subclass of ContactAssignment (via single table inheritence)

  • APLEntity is the class that has the @OneToMany to a subclass (ExternalContactAssignment)

  • ContactAssignment is the superclass that has the @ManyToOne back-reference to APLEntity

The code looked like this:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="internal_ind", discriminatorType = DiscriminatorType.STRING)
@Table(name="CONTACT_ASSIGNMENT")
public abstract class ContactAssignment {

// bidirectional
@ManyToOne
@JoinColumn(name="APL_ENTITY_SEQ_NUM", nullable=false)
private APLEntity aplEntity;
...

}

@Entity
@DiscriminatorValue("N")
public class ExternalContactAssignment extends
ContactAssignment {

// nothing relevant in this class
...
}


@Entity
@org.hibernate.annotations.Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="APL_ENTITY")
public abstract class APLEntity
implements ExternalContactAssignable {

@OneToMany(mappedBy="aplEntity")
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
@org.hibernate.annotations.Where(clause="internal_ind='N'")
private List externalContactAssignments;
...

}

Note: I'm stripping the code down to the bare minimum here (the real classes have lots of other stuff not relevant to the problem).

According to Emmanuel Bernard (Hibernate developer), it is semantically incorrect to assume this mapping structure to work as expected.

There are three approaches, which vary in

  • How much schema change you'll live with

    • Can you add new tables?

    • Can you add new columns?

  • Tradeoffs between relational integrity and OO-to-relational consistency

  • If you can live with superclass not being an @Entity

Here are the three approaches...

@MappedSuperclass with Column per Subclass

Approach:

  • ContactAssignment is mapped with an @MappedSuperclass instead of an @Entity.

  • ContactAssignment (superclass) maintains the references to APLEntity

  • Table per subclass model (new table(s) required). Discriminators cannot be used (they'll be ignored).

By using @MappedSuperclass, you lose the ability to have a relationship to the superclass. You also lose polymorphic queries when using straight JPA--although Hibernate queries will still be polymorphic.

I didn't pursue this as I'm trying to minimize schema change (an evaluation criteria for Hibernate). However, the mapping would look something like this:

@MappedSuperclass
public abstract class ContactAssignment {
...
@ManyToOne
@JoinColumn(name = "APL_ENTITY_SEQ_NUM", nullable = false)
public APLEntity getAplEntity() {
return aplEntity;
}

public void setAplEntity(APLEntity aplEntity) {
this.aplEntity = aplEntity;
}

}


@Entity
@Table(name="EXTERNAL_CONTACT_ASSIGNMENT") // class-specific
public class ExternalContactAssignment extends ContactAssignment {

// not much needed

}


@Entity
@org.hibernate.annotations.Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "APL_ENTITY")
public abstract class APLEntity {

@OneToMany(mappedBy="aplEntity")
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
@org.hibernate.annotations.Where(clause="internal_ind='N'")
private List externalContactAssignments;

...

}

Relationship Column per Subclass with Discriminator Column

Approach:

  • Single table model

  • ExternalContactAssignment (subclass) maintains the references to !APLEntity

  • Distinct relationship (FK) column used for each subclass

  • Still requires a discriminator column

  • Duplicative @Where and @DiscriminatorColumn's (bit of a wart).

This is Emmanuel Bernard's recommended approach, as being the most consistent between the object model and the relational model.

Consequences are that pure JPA queries are no longer polymorphic (but Hibernate queries still should be). You can no longer have a relationship to the superclass ContactAssignment. It feels less "OO" since you are forced to push the relationship down to the subclass(es)... I want OO considerations to drive this, not O/R mapping considerations (transparency!). On the relational side, things get ugly. Each subclass requires its own FK column out to the APL_ENTITY table. Although this is probably why Emmanuel says it's the most consistent, I don't think it's worth the price. XOR columns like that don't play well with referential integrity. A given row should only have one value populated no matter how many subclasses you have. It make it harder to query and index, and conceptually make the design harder to understand--and it only gets worse as you add more subclasses to the mix. It also doesn't make sense to me to have a discriminator column and still require multiple FK cols. Here it is:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="internal_ind", discriminatorType = DiscriminatorType.STRING)
@Table(name="CAU_CONTACT_ASSIGNMENT") // Note: referencing a new modified table
public abstract class ContactAssignment {

public abstract void setAplEntity(APLEntity aplEntity);

public abstract APLEntity getAplEntity();
}


@Entity
@DiscriminatorValue("N")
public class ExternalContactAssignment extends
ContactAssignment {

// bidirectional
@ManyToOne
@JoinColumn(name="EXT_APL_ENTITY_SEQ_NUM")
private APLEntity aplEntity;

@Override
public APLEntity getAplEntity() {
return aplEntity;
}

@Override
public void setAplEntity(APLEntity aplEntity) {
this.aplEntity = aplEntity;
}
}


@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="APL_ENTITY")
public abstract class APLEntity
implements ExternalContactAssignable {


@OneToMany(mappedBy="aplEntity", cascade={CascadeType.ALL})
@org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
@org.hibernate.annotations.Where(clause="internal_ind='N'")
private List externalContactAssignments;

}

Unidirectional Read Only Back-Reference

If I understand this correctly, this is basically ignoring Hibernate's ability to manage a bidirection relationship, and mapping a unidirectional read-only back reference from ContactAssignment to APLEntity. I believe this is what is discussed in Section 6.4.3 of Java Persistence With Hibernate. Making the back reference read-only tells Hibernate not to do a duplicative update when a ContactAssignment changes an APLEntity reference. Approach:

  • ContactAssignment is mapped with an @Entity.

  • ContactAssignment (superclass) maintains the references to !APLEntity

  • Single table model

  • Still requires a discriminator column

  • Duplicative @Where and @DiscriminatorColumn's (bit of a wart).

Emmanuel describes this approach as making the data design weaker. I'm not sure exactly how that is (or maybe what it means), or what tradeoff's are implied, but it's certainly is closest to what I was looking for:

  • no schema change required --> so no loss of relational integrity possible

  • scales easily with additional subclasses

  • let's OO considerations drive domain model design

Here's what it looks like

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="internal_ind", discriminatorType = DiscriminatorType.STRING)
@Table(name="CONTACT_ASSIGNMENT")
public abstract class ContactAssignment {

@ManyToOne
@JoinColumn(name="APL_ENTITY_SEQ_NUM", nullable=false)
private APLEntity aplEntity;

...
}

@Entity
@DiscriminatorValue("N")
public class ExternalContactAssignment extends
ContactAssignment {

// not much needed
}


@Entity
@org.hibernate.annotations.Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="APL_ENTITY")
public abstract class APLEntity
implements ExternalContactAssignable, Authorizable {


@OneToMany(cascade={CascadeType.ALL})
@JoinColumn(name="APL_ENTITY_SEQ_NUM", insertable=false, updatable=false)
@org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
@org.hibernate.annotations.Where(clause="internal_ind='N'")
private List externalContactAssignments;

...
}
Anyway, so I'll be moving ahead with this last approach as it is closest to what I want.

21 comments:

Erik said...

Thanks for the post. I was searching for some details about other classes referencing an @MappedSuperclass through @ManyToOne or @OneToMany and this information has been pretty useful.

James said...

Thanks for this post - you just saved me a lot of time. In my case, the database schema was entirely inflexible, and so I picked the last option. It seems awkward to duplicate the discriminator with the @Where annotation, but it solves the problem.

I'll just have to figure out why hibernate can't do the right thing with OO inheritance in the first place...

Clark Updike said...

I'm glad it's helpful--it was painful learning at the time. Regarding why hibernate doesn't work like we expect regarding inheritance in this case, hibernate folks have explained that it's not technically correct to expect it to work they way developers guess it would work. I don't have nearly enough depth in hibernate to claim otherwise... it's just unfortunate that it violates the principle of least surprise.

Priyatam said...
This comment has been removed by the author.
Georgy Bolyuba said...

Hi Clark, I wonder if you can send me the link to this statment fo hibernate team: "... hibernate folks have explained that it's not technically correct to expect it to work they way developers guess it would work." I would really appreciate that.

Clark said...

Sorry Georgy, but I'm fairly certain that it was in a paid JBoss support forum, which I can't even log into at the moment. I'm almost positive it was Emmanuel Bernard that gave me all the feedback on the approaches. Perhaps you can contact him.

Georgy Bolyuba said...

Got it. Will try to contact him. Thank you for quick reply

Anonymous said...

There is a bug in the Hibernate Jira system that seems related,

http://opensource.atlassian.com/projects/hibernate/browse/ANN-720

Amit said...

awesome work man, I spent litterally couple of days dazing over this issue, Thanks a lot

Clark said...

I think collectively quite a few of us have dazed over this issue :-) That's exactly why I blogged it--I'm glad it was helpful.

Rajeev said...

This is a good post. We are using Inheritance.Joined and this article u posted clarified the other options. BTW, ur 3rd option should complete it as good overview of Hibernate and JPA

Anonymous said...

thanks so much. It's a shame Hibernate continues to be non-intuitive ...

Christopher Wong said...

I want to thank you for posting this. Even though you wrote it 2 years ago, your post helped me immensely in my own struggle with this issue. I have a supplementary blog entry about this issue which includes a link to a forum posting with Emmanuel Bernard's explanation of Hibernate's behavior. Again, thanks.

Anonymous said...

=> Is it possible to have the following mapping ? What should I change ?
because if I try to query it :
Criteria crit = super.getSession().createCriteria(TA.class);
crit.createAlias("TE", "trk");
crit.add(Restrictions.like("trk.id", "%"));

I get :
select ...............
from TA this_
inner join TE1 trk1_ on this_.TE_ENTITY_FK=trk1_.TE_KEY
inner join TE2 trk1_ on this_.TE_ENTITY_FK=trk1_.TE_KEY
where trk1_.TE_KEY like ?

ORA-00918: column ambiguously defined.

Thank you ORM !


@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class TE {

@Id
@GenericGenerator(name = "idGenerator", strategy = "assigned")
@GeneratedValue(generator = "idGenerator")
@Column(name="TE_KEY")
private String id;

@OneToMany
@JoinColumn(name="TA_KEY", referencedColumnName="TE_KEY", insertable=false, updatable=false)
private List< TA > taList;
}

@Entity
public class TE1 extends TE {
}

@Entity
public class TE2 extends TE {
}


@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="ta_type")
public abstract class TA {

@Id
@GenericGenerator(name = "idGenerator", strategy = "assigned")
@GeneratedValue(generator = "idGenerator")
@Column(name="TA_KEY")
private String id;

public abstract void setTE(TE TE);

public abstract TE getTE();
}

@Entity
@DiscriminatorValue("TE1")
public class TA1 extends TA {

@ManyToOne(targetEntity=TE1.class)
@JoinColumn(name="TE_ENTITY_FK")
private TE TE;

public TE getTE() {
return TE;
}

public void setTE(TE TE) {
this.TE = TE;
}
}

@Entity
@DiscriminatorValue("TE2")
public class TA2 extends TA {

@ManyToOne(targetEntity=TE2.class)
@JoinColumn(name="TE_ENTITY_FK")
private TE TE;

public TE getTE() {
return TE;
}

public void setTE(TE TE) {
this.TE = TE;
}

}

Jerome C. said...

Hello, many thanks for this post.

Here is another solution:
Pet: single table inheritance with all properties but no manyToOne properties.

PetOwner abstract @MappedSuperClass inherit Pet and contains the owner manyToOne (and so owner can mapped by this property due to mappedsuperclass)

Cat and Dog inherit PetOwner with a discriminatorValue

This solution allows polymorphism, single table inheritance, discriminator value and mapped by property

Clark Updike said...

@Anonymous
Sorry, I haven't really done any work with Hibernate since posting this, so your best bet is to try the forums, or maybe try Jerome's idea.

@Jerome
Thanks for the alternative--if a schema change is permissible, that looks relatively clean.

Anonymous said...

"Pet: single table inheritance with all properties but no manyToOne properties.

PetOwner abstract @MappedSuperClass inherit Pet and contains the owner manyToOne (and so owner can mapped by this property due to mappedsuperclass)

Cat and Dog inherit PetOwner with a discriminatorValue
"

=> Cat and Dog inherit Pet. CatOwner and DogOwner inherit Owner. A Cat has a CatOwner. A Dog has a DogOwner. When fetching all Cat and Dog (All Pets), I want to inner join fetch CatOwner for Cat and DogOwner for Dog.

=> select p from Pet ...
must return query like this :
select ...............
from Pet this_
inner join CatOwner catowner1_ on this_.OWNER_FK=catowner1_.KEY
inner join DogOwner dogowner1_ on this_.OWNER_FK=dogowner_.KEY

But with your mapping I have something like this :
select ...............
from Pet this_
inner join (select ... DogOwner union CatOwner) owner_ on owner_.KEY = this_.KEY
=> It has very bad performance ! For each Pet record, Hibernate generates an SQL UNION between all Owner subclasses to select the right Owner. Suppose Owner is a big inheritance tree...

Jerome C. said...

I don't know if I really understand what you are saying but I don't have such union.

I don't have CatOwner and DogOwner, just one PetOwner. Here is the detailled example:

Pet: single table inheritance with all properties but no manyToOne properties.


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "petType", discriminatorType = DiscriminatorType.STRING)
public abstract class Pet
{
// all properties but no oneToMany
...
}


PetOwner abstract @MappedSuperClass inherit Pet and contains the owner manyToOne (and so owner can mapped by this property due to mappedsuperclass). NOTE the use of @Entity and @MappedSuperClass (if you ommit @entity, hibernate throws an error):

@Entity
@MappedSuperClass
public abstract class PetOwner extends Pet
{
private Owner owner;

@ManyToOne
@JoinColumn(name = "ownerId")
public Owner getOwner()
{
return owner;
}

public void setOwner(Owner owner)
{
this.owner = owner;
}
}


Cat and Dog inherit PetOwner with a discriminatorValue

@Entity
@DiscriminatorValue("dog")
public class Dog extends PetOwner
{

}

@Entity
@DiscriminatorValue("cat")
public class Cat extends PetOwner
{

}



When doing that, no performance problem, simple select and all runs perfectly. The only disadvantage is the intermediate class PetOwner. But once is done, it's not really a problem.

Anonymous said...

I want to have :

@Entity
@DiscriminatorValue("dog")
public class Dog extends PetOwner
{

private Z a;

@ManyToOne
@JoinColumn(name = "zId")
public A getA()
{
return a;
}


}

@Entity
@DiscriminatorValue("cat")
public class Cat extends PetOwner
{
private Z b;

@ManyToOne
@JoinColumn(name = "zId")
public B getB()
{
return b;
}

}

A and B inherit abstract Z (table per class inheritance)

=> the manyToOne is associated with a superclass Z, but only one sublass of Z corresponds to Dog (ie, for Cat)

Anonymous said...

I put the ManyToOne in PetOwner, then hibernate generates A union B for each PetOwner...

Anonymous said...

There are aion kinah 3 men in a plane.world of warcraft gold A priest,cdkey a baseball player world of warcraft power leveling and a soldier.dog clothing the pilot goes into dog clothes the cabin maple story mesos and tells them that there flyff gold is a weight overload 2moons power leveling on the plane Aion kina and wedding dresses they would wow gold each have to throw one personal belonging out aoc power leveling the window to cabal alz safely land. the priest throws flyff money out a bible and says,buy wow gold forgive me lord, i will last chaos gold get anotherThe baseball player world of warcraft power leveling throws a baseball flyff gold out and says, aoc gold i play professional baseball,world of warcraft gold i can get maple story mesos these whenever dragonica gold i want.Now