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.

Wednesday, January 10, 2007

OC4J Rmi Client: NoClassDefFoundError: javax/ejb/EJBHome

Trying to run an OC4J rmi client where the oc4jclient.jar was packaged with the project (no longer in the jdeveloper home directory) produced some vexing problems with the following error:
java.lang.NoClassDefFoundError: javax/ejb/EJBHome
at java.lang.Class.getDeclaredMethods0(Native Method)
...
despite having the ejb.jar file on the classpath. After much frustration, it turns out that oc4jclient.jar uses a Class-Path attribute in its manifest:
Class-Path: lib/ejb.jar lib/mail.jar lib/oc4j_orb.jar lib/orbbase.jar
lib/iiop_support.jar lib/jms.jar lib/jta.jar ../../lib/xmlparserv2.ja
r ../../opmn/lib/optic.jar ../../oracle/jlib/oraclepki.jar ../../jlib
/oraclepki.jar ../../oracle/jlib/ojpse.jar ../../jlib/ojpse.jar
The problem seems to be that although the application classloader does have ejb.jar on the classpath, the oc4jclient.jar gets loaded with a different classloader that can't see it, and expects the ejb.jar to be in a lib dir relative to itself. Turns out you can ignore all the other jar references, only ejb.jar is required (luckily). So, where ever you place the oc4jclient.jar, just create a lib dir in the same directory and drop the ejb.jar into it--then the problem should be solved.

Another bit of weirdness was that it wouldn't seem to work with jdk 1.5.0_06. We looked in the ext dir for anything that might be messing it up but didn't find anything obvious. It seems too bizarre to the the jdk version was also causing a problem, but simply switching to 1.5.0_02 would fix other classpath problems.