First off, yes I know this approach is not recommended. Until Hibernate supports get(), load(), second level cache, etc with natural-id this is my only option.
I have an entity with a composite @EmbeddedId used for the primary key, and a generated unique surrogate ID that I use for FK associations to prevent the child tables from needing all the fields of the composite key.
Basically looks like this:
Code:
@Embeddable
class ParentId {
String name;
Date effectiveDate;
}
class Parent {
@EmbeddedId
ParentId compositeId;
@Column(name="generatedId", unique=true)
String generatedId;
@OneToMany(mappedBy="parent")
Set<Child> children;
}
class Child {
@ManyToOne
@JoinColumn(name="parentId", referencedColumnName="generatedId")
Parent parent;
}
Any time I try to lazy load or join the child collection in a criteria or HQL query, an IllegalArgumentException results because Hibernate establishes the owner (parent) object's key as itself rather than as an instance of it's composite key class.
The offending code looks to be this part of org.hibernate.type.CollectionType, which seems to ignore the possibility that that the owner object might have a composite embedded key:
Code:
public Serializable getKeyOfOwner(Object owner, SessionImplementor session) {
EntityEntry entityEntry = session.getPersistenceContext().getEntry( owner );
if ( entityEntry == null ) return null; // This just handles a particular case of component
// projection, perhaps get rid of it and throw an exception
if ( foreignKeyPropertyName == null ) {
return entityEntry.getId();
}
else {
// TODO: at the point where we are resolving collection references, we don't
// know if the uk value has been resolved (depends if it was earlier or
// later in the mapping document) - now, we could try and use e.getStatus()
// to decide to semiResolve(), trouble is that initializeEntity() reuses
// the same array for resolved and hydrated values
Object id;
[b] if ( entityEntry.getLoadedState() != null ) {
id = entityEntry.getLoadedValue( foreignKeyPropertyName );
}[/b]
else {
id = entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName, session.getEntityMode() );
}
// NOTE VERY HACKISH WORKAROUND!!
// TODO: Fix this so it will work for non-POJO entity mode
Type keyType = getPersister( session ).getKeyType();
if ( !keyType.getReturnedClass().isInstance( id ) ) {
id = (Serializable) keyType.semiResolve(
entityEntry.getLoadedValue( foreignKeyPropertyName ),
session,
owner
);
}
return (Serializable) id;
}
}
Now that the key has been established as the actual entity and not the ID class, a trainwreck is impending when I eventually hit this piece of code in org.hibernate.engine.StatefulPersistenceContext:
Code:
/**
* Get the entity that owns this persistent collection
*/
public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException {
return getEntity( new EntityKey( key, collectionPersister.getOwnerEntityPersister(), session.getEntityMode() ) );
}
In the consructor for EntityKey Hibernate attempts to generate a hashcode for the key by invoking the getters of my composite ID class. Problem is that the actual target object is an instance of the entity class not the ID class. End result is an IllegalArgumentException due to the mismatched classes.
I would really appreciate either confirmation that this is a bug or advice on how I can map this association to avoid this issue.
Thanks