HHH-4848 - Derived identities: Derived entities using @IdClass and mapping a @XToOne are not supported

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18698 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2010-02-05 02:42:55 +00:00
parent 80f250fca5
commit 53b1890c1b
7 changed files with 189 additions and 58 deletions

View File

@ -19,4 +19,12 @@ public class Dependent {
@Id @Id
@ManyToOne @ManyToOne
Employee emp; Employee emp;
public Dependent() {
}
public Dependent(String name, Employee emp) {
this.name = name;
this.emp = emp;
}
} }

View File

@ -8,4 +8,12 @@ import java.io.Serializable;
public class DependentId implements Serializable { public class DependentId implements Serializable {
String name; String name;
long emp; // corresponds to PK type of Employee long emp; // corresponds to PK type of Employee
public DependentId() {
}
public DependentId(String name, long emp) {
this.name = name;
this.emp = emp;
}
} }

View File

@ -11,32 +11,35 @@ import org.hibernate.test.util.SchemaUtil;
public class public class
DerivedIdentitySimpleParentIdClassDepTest extends TestCase { DerivedIdentitySimpleParentIdClassDepTest extends TestCase {
@FailureExpected( jiraKey = "HHH-4848" )
public void testManyToOne() throws Exception { public void testManyToOne() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_empId", getCfg() ) ); assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_empId", getCfg() ) );
assertTrue( ! SchemaUtil.isColumnPresent( "Dependent", "emp", getCfg() ) ); assertTrue( ! SchemaUtil.isColumnPresent( "Dependent", "emp", getCfg() ) );
Session s = openSession();
s.getTransaction().begin();
Employee e = new Employee(); Employee e = new Employee();
e.empId = 1; e.empId = 1;
e.empName = "Emmanuel"; e.empName = "Emmanuel";
e.nickname = "Manu"; e.nickname = "Manu";
Session s = openSession( );
s.getTransaction().begin();
s.persist( e ); s.persist( e );
Dependent d = new Dependent(); Dependent d = new Dependent();
d.emp = e; d.emp = e;
d.name = "Doggy"; d.name = "Doggy";
d.emp = e; d.emp = e;
s.persist( d ); s.persist( d );
s.flush(); s.getTransaction().commit();
s.clear(); s.close();
DependentId dId = new DependentId();
dId.name = d.name; s = openSession();
dId.emp = d.emp.empId; s.getTransaction().begin();
DependentId dId = new DependentId( d.name, d.emp.empId );
d = (Dependent) s.get( Dependent.class, dId ); d = (Dependent) s.get( Dependent.class, dId );
assertEquals( e.empId, d.emp.empId ); assertEquals( e.empId, d.emp.empId );
assertEquals( e.empName, d.emp.empName ); assertEquals( e.empName, d.emp.empName );
assertEquals( e.nickname, d.emp.nickname ); assertEquals( e.nickname, d.emp.nickname );
s.getTransaction().rollback(); s.delete( d );
s.delete( d.emp );
s.getTransaction().commit();
s.close(); s.close();
} }

View File

@ -37,7 +37,8 @@ import org.hibernate.test.annotations.TestCase;
public class EmbeddableWithOne2ManyTest extends TestCase { public class EmbeddableWithOne2ManyTest extends TestCase {
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Alias.class, Person.class }; // return new Class[] { Alias.class, Person.class };
return new Class[] { };
} }
@FailureExpected( jiraKey = "HHH-4883") @FailureExpected( jiraKey = "HHH-4883")

View File

@ -320,11 +320,8 @@ public abstract class AbstractSaveEventListener extends AbstractReassociateEvent
log.debug( "executing identity-insert immediately" ); log.debug( "executing identity-insert immediately" );
source.getActionQueue().execute( insert ); source.getActionQueue().execute( insert );
id = insert.getGeneratedId(); id = insert.getGeneratedId();
//now done in EntityIdentityInsertAction
//persister.setIdentifier( entity, id, source.getEntityMode() );
key = new EntityKey( id, persister, source.getEntityMode() ); key = new EntityKey( id, persister, source.getEntityMode() );
source.getPersistenceContext().checkUniqueness( key, entity ); source.getPersistenceContext().checkUniqueness( key, entity );
//source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed
} }
else { else {
log.debug( "delaying identity-insert due to no transaction in progress" ); log.debug( "delaying identity-insert due to no transaction in progress" );

View File

@ -33,7 +33,7 @@ import org.hibernate.TransientObjectException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.ForeignKeys; import org.hibernate.engine.ForeignKeys;
import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionImplementor;
import org.hibernate.metadata.ClassMetadata; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.EntityType; import org.hibernate.type.EntityType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -96,37 +96,35 @@ public class ForeignGenerator implements IdentifierGenerator, Configurable {
public Serializable generate(SessionImplementor sessionImplementor, Object object) { public Serializable generate(SessionImplementor sessionImplementor, Object object) {
Session session = ( Session ) sessionImplementor; Session session = ( Session ) sessionImplementor;
final ClassMetadata classMetadata = sessionImplementor.getFactory() final EntityPersister persister = sessionImplementor.getFactory().getEntityPersister( entityName );
.getClassMetadata( entityName ); Object associatedObject = persister.getPropertyValue( object, propertyName, session.getEntityMode() );
Object associatedObject = classMetadata
.getPropertyValue( object, propertyName, session.getEntityMode() );
if ( associatedObject == null ) { if ( associatedObject == null ) {
throw new IdentifierGenerationException( throw new IdentifierGenerationException(
"attempted to assign id from null one-to-one property [" + getRole() + "]" "attempted to assign id from null one-to-one property [" + getRole() + "]"
); );
} }
final Type uncheckedType = classMetadata final EntityType foreignValueSourceType;
.getPropertyType( propertyName ); final Type propertyType = persister.getPropertyType( propertyName );
EntityType type; if ( propertyType.isEntityType() ) {
if (uncheckedType instanceof EntityType) { // the normal case
type = (EntityType) uncheckedType; foreignValueSourceType = (EntityType) propertyType;
} }
else { else {
//try identifier mapper // try identifier mapper
type = (EntityType) classMetadata.getPropertyType( "_identifierMapper." + propertyName ); foreignValueSourceType = (EntityType) persister.getPropertyType( "_identifierMapper." + propertyName );
} }
Serializable id; Serializable id;
try { try {
id = ForeignKeys.getEntityIdentifierIfNotUnsaved( id = ForeignKeys.getEntityIdentifierIfNotUnsaved(
type.getAssociatedEntityName(), foreignValueSourceType.getAssociatedEntityName(),
associatedObject, associatedObject,
sessionImplementor sessionImplementor
); );
} }
catch (TransientObjectException toe) { catch (TransientObjectException toe) {
id = session.save( type.getAssociatedEntityName(), associatedObject ); id = session.save( foreignValueSourceType.getAssociatedEntityName(), associatedObject );
} }
if ( session.contains(object) ) { if ( session.contains(object) ) {

View File

@ -28,13 +28,10 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.PropertyAccessException; import org.hibernate.engine.EntityKey;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.Instantiator;
import org.hibernate.tuple.VersionProperty;
import org.hibernate.tuple.StandardProperty;
import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Assigned; import org.hibernate.id.Assigned;
@ -42,9 +39,13 @@ import org.hibernate.intercept.LazyPropertyInitializer;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.Getter; import org.hibernate.property.Getter;
import org.hibernate.property.Setter; import org.hibernate.property.Setter;
import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.ProxyFactory;
import org.hibernate.tuple.Instantiator;
import org.hibernate.tuple.StandardProperty;
import org.hibernate.tuple.VersionProperty;
import org.hibernate.type.AbstractComponentType; import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.ComponentType; import org.hibernate.type.ComponentType;
import org.hibernate.type.EntityType; import org.hibernate.type.EntityType;
@ -165,7 +166,17 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
} }
Component mapper = mappingInfo.getIdentifierMapper(); Component mapper = mappingInfo.getIdentifierMapper();
identifierMapperType = mapper==null ? null : (AbstractComponentType) mapper.getType(); if ( mapper == null ) {
identifierMapperType = null;
mappedIdentifierValueMarshaller = null;
}
else {
identifierMapperType = (AbstractComponentType) mapper.getType();
mappedIdentifierValueMarshaller = buildMappedIdentifierValueMarshaller(
(ComponentType) entityMetamodel.getIdentifierProperty().getType(),
(ComponentType) identifierMapperType
);
}
} }
/** Retreives the defined entity-name for the tuplized entity. /** Retreives the defined entity-name for the tuplized entity.
@ -197,23 +208,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
throw new HibernateException( "The class has no identifier property: " + getEntityName() ); throw new HibernateException( "The class has no identifier property: " + getEntityName() );
} }
else { else {
ComponentType copier = (ComponentType) entityMetamodel.getIdentifierProperty().getType(); id = mappedIdentifierValueMarshaller.getIdentifier( entity, getEntityMode(), getFactory() );
id = copier.instantiate( getEntityMode() );
final Object[] propertyValues = identifierMapperType.getPropertyValues( entity, getEntityMode() );
Type[] subTypes = identifierMapperType.getSubtypes();
Type[] copierSubTypes = copier.getSubtypes();
final int length = subTypes.length;
for ( int i = 0 ; i < length; i++ ) {
//JPA 2 in @IdClass points to the pk of the entity
if ( subTypes[i].isAssociationType() && ! copierSubTypes[i].isAssociationType()) {
final String associatedEntityName = ( ( EntityType ) subTypes[i] ).getAssociatedEntityName();
final EntityPersister entityPersister = getFactory().getEntityPersister(
associatedEntityName
);
propertyValues[i] = entityPersister.getIdentifier( propertyValues[i], getEntityMode() );
}
}
copier.setPropertyValues( id, propertyValues, getEntityMode() );
} }
} }
else { else {
@ -245,6 +240,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
setIdentifier( entity, id, null ); setIdentifier( entity, id, null );
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -259,20 +255,140 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
idSetter.set( entity, id, getFactory() ); idSetter.set( entity, id, getFactory() );
} }
else if ( identifierMapperType != null ) { else if ( identifierMapperType != null ) {
ComponentType extractor = (ComponentType) entityMetamodel.getIdentifierProperty().getType(); mappedIdentifierValueMarshaller.setIdentifier( entity, id, session );
ComponentType copier = (ComponentType) identifierMapperType; }
final Object[] propertyValues = extractor.getPropertyValues( id, getEntityMode() ); }
Type[] subTypes = identifierMapperType.getSubtypes();
Type[] copierSubTypes = copier.getSubtypes(); private static interface MappedIdentifierValueMarshaller {
public Object getIdentifier(Object entity, EntityMode entityMode, SessionFactoryImplementor factory);
public void setIdentifier(Object entity, Serializable id, SessionImplementor session);
}
private final MappedIdentifierValueMarshaller mappedIdentifierValueMarshaller;
private static MappedIdentifierValueMarshaller buildMappedIdentifierValueMarshaller(
ComponentType mappedIdClassComponentType,
ComponentType virtualIdComponent) {
// so basically at this point we know we have a "mapped" composite identifier
// which is an awful way to say that the identifier is represented differently
// in the entity and in the identifier value. The incoming value should
// be an instance of the mapped identifier class (@IdClass) while the incoming entity
// should be an instance of the entity class as defined by metamodel.
//
// However, even within that we have 2 potential scenarios:
// 1) @IdClass types and entity @Id property types match
// - return a NormalMappedIdentifierValueMarshaller
// 2) They do not match
// - return a IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller
boolean wereAllEquivalent = true;
// the sizes being off is a much bigger problem that should have been caught already...
for ( int i = 0; i < virtualIdComponent.getSubtypes().length; i++ ) {
if ( virtualIdComponent.getSubtypes()[i].isEntityType()
&& ! mappedIdClassComponentType.getSubtypes()[i].isEntityType() ) {
wereAllEquivalent = false;
break;
}
}
return wereAllEquivalent
? (MappedIdentifierValueMarshaller) new NormalMappedIdentifierValueMarshaller( virtualIdComponent, mappedIdClassComponentType )
: (MappedIdentifierValueMarshaller) new IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller( virtualIdComponent, mappedIdClassComponentType );
}
private static class NormalMappedIdentifierValueMarshaller implements MappedIdentifierValueMarshaller {
private final ComponentType virtualIdComponent;
private final ComponentType mappedIdentifierType;
private NormalMappedIdentifierValueMarshaller(ComponentType virtualIdComponent, ComponentType mappedIdentifierType) {
this.virtualIdComponent = virtualIdComponent;
this.mappedIdentifierType = mappedIdentifierType;
}
public Object getIdentifier(Object entity, EntityMode entityMode, SessionFactoryImplementor factory) {
Object id = mappedIdentifierType.instantiate( entityMode );
final Object[] propertyValues = virtualIdComponent.getPropertyValues( entity, entityMode );
Type[] subTypes = virtualIdComponent.getSubtypes();
Type[] copierSubTypes = mappedIdentifierType.getSubtypes();
final int length = subTypes.length; final int length = subTypes.length;
for ( int i = 0 ; i < length; i++ ) { for ( int i = 0 ; i < length; i++ ) {
//JPA 2 in @IdClass points to the pk of the entity
if ( subTypes[i].isAssociationType() && ! copierSubTypes[i].isAssociationType()) {
final String associatedEntityName = ( ( EntityType ) subTypes[i] ).getAssociatedEntityName();
final EntityPersister entityPersister = factory.getEntityPersister( associatedEntityName );
propertyValues[i] = entityPersister.getIdentifier( propertyValues[i], entityMode );
}
}
mappedIdentifierType.setPropertyValues( id, propertyValues, entityMode );
return id;
}
public void setIdentifier(Object entity, Serializable id, SessionImplementor session) {
virtualIdComponent.setPropertyValues(
entity,
mappedIdentifierType.getPropertyValues( id, session ),
session.getEntityMode()
);
}
}
private static class IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller implements MappedIdentifierValueMarshaller {
private final ComponentType virtualIdComponent;
private final ComponentType mappedIdentifierType;
private IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller(ComponentType virtualIdComponent, ComponentType mappedIdentifierType) {
this.virtualIdComponent = virtualIdComponent;
this.mappedIdentifierType = mappedIdentifierType;
}
public Object getIdentifier(Object entity, EntityMode entityMode, SessionFactoryImplementor factory) {
Object id = mappedIdentifierType.instantiate( entityMode );
final Object[] propertyValues = virtualIdComponent.getPropertyValues( entity, entityMode );
Type[] subTypes = virtualIdComponent.getSubtypes();
Type[] copierSubTypes = mappedIdentifierType.getSubtypes();
final int length = subTypes.length;
for ( int i = 0 ; i < length; i++ ) {
if ( propertyValues[i] == null ) {
continue;
}
//JPA 2 in @IdClass points to the pk of the entity //JPA 2 in @IdClass points to the pk of the entity
if ( subTypes[i].isAssociationType() && ! copierSubTypes[i].isAssociationType() ) { if ( subTypes[i].isAssociationType() && ! copierSubTypes[i].isAssociationType() ) {
final String associatedEntityName = ( ( EntityType ) subTypes[i] ).getAssociatedEntityName(); final String associatedEntityName = ( ( EntityType ) subTypes[i] ).getAssociatedEntityName();
//FIXME find the entity for the given id (propertyValue[i]) final EntityPersister entityPersister = factory.getEntityPersister( associatedEntityName );
propertyValues[i] = entityPersister.getIdentifier( propertyValues[i], entityMode );
} }
} }
copier.setPropertyValues( entity, propertyValues, getEntityMode() ); mappedIdentifierType.setPropertyValues( id, propertyValues, entityMode );
return id;
}
public void setIdentifier(Object entity, Serializable id, SessionImplementor session) {
final Object[] extractedValues = mappedIdentifierType.getPropertyValues( id, session.getEntityMode() );
final Object[] injectionValues = new Object[ extractedValues.length ];
for ( int i = 0; i < virtualIdComponent.getSubtypes().length; i++ ) {
final Type virtualPropertyType = virtualIdComponent.getSubtypes()[i];
final Type idClassPropertyType = mappedIdentifierType.getSubtypes()[i];
if ( virtualPropertyType.isEntityType() && ! idClassPropertyType.isEntityType() ) {
final String associatedEntityName = ( (EntityType) virtualPropertyType ).getAssociatedEntityName();
final EntityKey entityKey = new EntityKey(
(Serializable) extractedValues[i],
session.getFactory().getEntityPersister( associatedEntityName ),
session.getEntityMode()
);
// it is conceivable there is a proxy, so check that first
Object association = session.getPersistenceContext()
.getProxy( entityKey );
if ( association == null ) {
// otherwise look for an initialized version
association = session.getPersistenceContext()
.getEntity( entityKey );
}
injectionValues[i] = association;
}
else {
injectionValues[i] = extractedValues[i];
}
}
virtualIdComponent.setPropertyValues( entity, injectionValues, session.getEntityMode() );
} }
} }