HHH-4529 support @ManyToOne and fix FK error

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18589 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Emmanuel Bernard 2010-01-20 16:51:02 +00:00
parent e655b54eb8
commit fd389b913e
15 changed files with 260 additions and 44 deletions

View File

@ -1379,12 +1379,13 @@ public final class AnnotationBinder {
joinColumn.setSecondaryTableName( join.getTable().getName() );
}
}
final boolean mandatory = !ann.optional() || property.isAnnotationPresent( MapsId.class );
bindManyToOne(
getCascadeStrategy( ann.cascade(), hibernateCascade, false),
joinColumns,
ann.optional(),
!mandatory,
ignoreNotFound, onDeleteCascade,
mappings.getReflectionManager().toXClass( ann.targetEntity() ),
ToOneBinder.getTargetEntity( inferredData, mappings ),
propertyHolder,
inferredData, false, isIdentifierMapper, inSecondPass, mappings
);
@ -1422,7 +1423,7 @@ public final class AnnotationBinder {
!mandatory,
getFetchMode( ann.fetch() ),
ignoreNotFound, onDeleteCascade,
mappings.getReflectionManager().toXClass( ann.targetEntity() ),
ToOneBinder.getTargetEntity( inferredData, mappings ),
propertyHolder,
inferredData, ann.mappedBy(), trueOneToOne, isIdentifierMapper, inSecondPass, mappings
);
@ -2117,11 +2118,14 @@ public final class AnnotationBinder {
catch ( ClassNotFoundException e ) {
throw new AssertionFailure( "Persistence class name cannot be converted into a Class", e);
}
final PropertyData annotatedWithMapsId = mappings.getPropertyAnnotatedWithMapsId( persistentXClass, "" );
if ( annotatedWithMapsId != null ) {
columns = buildExplicitJoinColumns( propertyHolder, annotatedWithMapsId.getProperty(), annotatedWithMapsId, entityBinder, mappings );
if (columns == null) {
columns = buildDefaultJoinColumnsForXToOne( propertyHolder, annotatedWithMapsId.getProperty(), annotatedWithMapsId, entityBinder, mappings );
throw new UnsupportedOperationException( "Implicit @JoinColumn is not supported on @MapsId properties: "
+ annotatedWithMapsId.getDeclaringClass() + " " + annotatedWithMapsId.getPropertyName() );
}
}
@ -2194,13 +2198,9 @@ public final class AnnotationBinder {
if ( unique ) {
value.markAsLogicalOneToOne();
}
if ( isDefault( targetEntity, mappings ) ) {
value.setReferencedEntityName( inferredData.getClassOrElementName() );
}
else {
value.setReferencedEntityName( targetEntity.getName() );
}
defineFetchingStrategy( value, inferredData.getProperty() );
value.setReferencedEntityName( ToOneBinder.getReferenceEntityName(inferredData, targetEntity, mappings) );
final XProperty property = inferredData.getProperty();
defineFetchingStrategy( value, property );
//value.setFetchMode( fetchMode );
value.setIgnoreNotFound( ignoreNotFound );
value.setCascadeDeleteEnabled( cascadeOnDelete );
@ -2210,11 +2210,18 @@ public final class AnnotationBinder {
column.setNullable( false );
}
}
if ( property.isAnnotationPresent( MapsId.class ) ) {
//read only
for (Ejb3JoinColumn column : columns) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
value.setTypeName( inferredData.getClassOrElementName() );
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
ForeignKey fk = inferredData.getProperty().getAnnotation( ForeignKey.class );
ForeignKey fk = property.getAnnotation( ForeignKey.class );
String fkName = fk != null ?
fk.name() :
"";
@ -2250,7 +2257,7 @@ public final class AnnotationBinder {
}
binder.setAccessType( inferredData.getDefaultAccess() );
binder.setCascade( cascadeStrategy );
binder.setProperty(inferredData.getProperty());
binder.setProperty( property );
Property prop = binder.make();
//composite FK columns are in the same table so its OK
propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() );

View File

@ -374,7 +374,7 @@ public class AnnotationConfiguration extends Configuration {
iter = secondPasses.iterator();
while ( iter.hasNext() ) {
SecondPass sp = ( SecondPass ) iter.next();
//do the SecondaryTable second pass before any association becasue associations can be built on joins
//do the SecondaryTable second pass before any association because associations can be built on joins
if ( sp instanceof SecondaryTableSecondPass ) {
sp.doSecondPass( classes );
iter.remove();

View File

@ -393,6 +393,31 @@ public class Ejb3JoinColumn extends Ejb3Column {
String logicalReferencedColumn = getMappings().getLogicalColumnName(
referencedColumn.getQuotedName(), referencedEntity.getTable()
);
columnName = buildDefaultColumnName( referencedEntity, logicalReferencedColumn );
//yuk side effect on an implicit column
setLogicalColumnName( columnName );
setReferencedColumn( logicalReferencedColumn );
initMappingColumn(
columnName,
null, referencedColumn.getLength(),
referencedColumn.getPrecision(),
referencedColumn.getScale(),
getMappingColumn() != null ? getMappingColumn().isNullable() : false,
referencedColumn.getSqlType(),
getMappingColumn() != null ? getMappingColumn().isUnique() : false,
false
);
linkWithValue( value );
}
public void addDefaultJoinColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) {
final String columnName = buildDefaultColumnName( referencedEntity, logicalReferencedColumn );
getMappingColumn().setName( columnName );
setLogicalColumnName( columnName );
}
private String buildDefaultColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) {
String columnName;
boolean mappedBySide = mappedByTableName != null || mappedByPropertyName != null;
boolean ownerSide = getPropertyName() != null;
@ -443,20 +468,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
columnName = StringHelper.quote( columnName );
}
}
//yuk side effect on an implicit column
setLogicalColumnName( columnName );
setReferencedColumn( logicalReferencedColumn );
initMappingColumn(
columnName,
null, referencedColumn.getLength(),
referencedColumn.getPrecision(),
referencedColumn.getScale(),
getMappingColumn() != null ? getMappingColumn().isNullable() : false,
referencedColumn.getSqlType(),
getMappingColumn() != null ? getMappingColumn().isUnique() : false,
false
);
linkWithValue( value );
return columnName;
}
/**

View File

@ -88,13 +88,7 @@ public class OneToOneSecondPass implements SecondPass {
);
final String propertyName = inferredData.getPropertyName();
value.setPropertyName( propertyName );
String referencedEntityName;
if ( AnnotationBinder.isDefault( targetEntity, mappings ) ) {
referencedEntityName = inferredData.getClassOrElementName();
}
else {
referencedEntityName = targetEntity.getName();
}
String referencedEntityName = ToOneBinder.getReferenceEntityName(inferredData, targetEntity, mappings);
value.setReferencedEntityName( referencedEntityName );
AnnotationBinder.defineFetchingStrategy( value, inferredData.getProperty() );
//value.setFetchMode( fetchMode );

View File

@ -34,7 +34,7 @@ import org.hibernate.cfg.annotations.SimpleValueBinder;
public class SetSimpleValueTypeSecondPass implements SecondPass {
SimpleValueBinder binder;
public SetSimpleValueTypeSecondPass(SimpleValueBinder val) {
binder = val;
}

View File

@ -0,0 +1,53 @@
package org.hibernate.cfg;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
/**
* Work in progress
* The goal of this class is to aggregate all operations
* related to ToOne binding operations
*
* @author Emmanuel Bernard
*/
public class ToOneBinder {
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, ExtendedMappings mappings) {
if ( AnnotationBinder.isDefault( targetEntity, mappings ) ) {
return propertyData.getClassOrElementName();
}
else {
return targetEntity.getName();
}
}
public static String getReferenceEntityName(PropertyData propertyData, ExtendedMappings mappings) {
XClass targetEntity = getTargetEntity( propertyData, mappings );
if ( AnnotationBinder.isDefault( targetEntity, mappings ) ) {
return propertyData.getClassOrElementName();
}
else {
return targetEntity.getName();
}
}
public static XClass getTargetEntity(PropertyData propertyData, ExtendedMappings mappings) {
XProperty property = propertyData.getProperty();
return mappings.getReflectionManager().toXClass( getTargetEntityClass( property ) );
}
private static Class<?> getTargetEntityClass(XProperty property) {
final ManyToOne mTo = property.getAnnotation( ManyToOne.class );
if (mTo != null) {
return mTo.targetEntity();
}
final OneToOne oTo = property.getAnnotation( OneToOne.class );
if (oTo != null) {
return oTo.targetEntity();
}
throw new AssertionFailure("Unexpected discovery of a targetEntity: " + property.getName() );
}
}

View File

@ -27,6 +27,7 @@ import java.io.Serializable;
import java.sql.Types;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import javax.persistence.Enumerated;
import javax.persistence.Lob;
@ -46,6 +47,7 @@ import org.hibernate.cfg.BinderHelper;
import org.hibernate.cfg.Ejb3Column;
import org.hibernate.cfg.ExtendedMappings;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.cfg.SecondPass;
import org.hibernate.cfg.SetSimpleValueTypeSecondPass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
@ -288,15 +290,13 @@ public class SimpleValueBinder {
table = columns[0].getTable();
}
simpleValue = new SimpleValue( table );
for (Ejb3Column column : columns) {
column.linkWithValue( simpleValue );
}
linkWithValue();
boolean isInSecondPass = mappings.isInSecondPass();
SetSimpleValueTypeSecondPass secondPass = new SetSimpleValueTypeSecondPass(this);
if (!isInSecondPass) {
//Defer this to the second pass
SetSimpleValueTypeSecondPass secondPass = new SetSimpleValueTypeSecondPass(this);
mappings.addSecondPass(secondPass);
}
else {
@ -306,6 +306,12 @@ public class SimpleValueBinder {
return simpleValue;
}
public void linkWithValue() {
for ( Ejb3Column column : columns) {
column.linkWithValue( simpleValue );
}
}
public void fillSimpleValue() {
log.debug( "setting SimpleValue typeName for {}", propertyName );

View File

@ -314,7 +314,7 @@ public class TableBinder {
) {
PersistentClass associatedClass;
if ( destinationEntity != null ) {
//overidden destination
//overridden destination
associatedClass = destinationEntity;
}
else {

View File

@ -0,0 +1,21 @@
package org.hibernate.test.annotations.derivedidentities.e1.b;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
/**
* @author Emmanuel Bernard
*/
@Entity
public class Dependent {
@EmbeddedId
DependentId id;
// id attribute mapped by join column default
@MapsId("empPK") // maps empPK attribute of embedded id
@ManyToOne
Employee emp;
}

View File

@ -0,0 +1,13 @@
package org.hibernate.test.annotations.derivedidentities.e1.b;
import java.io.Serializable;
import javax.persistence.Embeddable;
/**
* @author Emmanuel Bernard
*/
@Embeddable
public class DependentId implements Serializable {
String name;
long empPK; // corresponds to PK type of Employee
}

View File

@ -0,0 +1,42 @@
package org.hibernate.test.annotations.derivedidentities.e1.b;
import org.hibernate.Session;
import org.hibernate.test.annotations.TestCase;
import org.hibernate.test.util.SchemaUtil;
/**
* @author Emmanuel Bernard
*/
public class DerivedIdentitySimpleParentEmbeddedIdDepTest extends TestCase {
// public void testIt() throws Exception {
// assertTrue( SchemaUtil.isColumnPresent( "Dependent", "empPK", getCfg() ) );
// assertTrue( ! SchemaUtil.isColumnPresent( "Dependent", "emp_empId", getCfg() ) );
// Employee e = new Employee();
// e.empId = 1;
// e.empName = "Emmanuel";
// Session s = openSession( );
// s.getTransaction().begin();
// s.persist( e );
// Dependent d = new Dependent();
// d.emp = e;
// d.id = new DependentId();
// d.id.name = "Doggy";
// d.id.empPK = e.empId; //FIXME not needed when foreign is enabled
// s.persist( d );
// s.flush();
// s.clear();
// d = (Dependent) s.get( Dependent.class, d.id );
// assertEquals( d.id.empPK, d.emp.empId );
// s.getTransaction().rollback();
// s.close();
// }
@Override
protected Class<?>[] getMappings() {
return new Class<?>[] {
Dependent.class,
Employee.class
};
}
}

View File

@ -0,0 +1,14 @@
package org.hibernate.test.annotations.derivedidentities.e1.b;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Emmanuel Bernard
*/
@Entity
public class Employee {
@Id
long empId;
String empName;
}

View File

@ -11,7 +11,7 @@ import org.hibernate.test.util.SchemaUtil;
*/
public class DerivedIdentitySimpleParentSimpleDepTest extends TestCase {
public void testIt() throws Exception {
public void testOneToOneExplicitJoinColumn() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "MedicalHistory", "FK", getCfg() ) );
assertTrue( ! SchemaUtil.isColumnPresent( "MedicalHistory", "id", getCfg() ) );
Person e = new Person();
@ -36,11 +36,37 @@ public class DerivedIdentitySimpleParentSimpleDepTest extends TestCase {
s.close();
}
public void testManyToOneExplicitJoinColumn() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "FinancialHistory", "FK", getCfg() ) );
assertTrue( ! SchemaUtil.isColumnPresent( "FinancialHistory", "id", getCfg() ) );
Person e = new Person();
e.ssn = "aaa";
Session s = openSession( );
s.getTransaction().begin();
s.persist( e );
FinancialHistory d = new FinancialHistory();
d.patient = e;
d.id = "aaa"; //FIXME not needed when foreign is enabled
s.persist( d );
s.flush();
s.clear();
d = (FinancialHistory) s.get( FinancialHistory.class, d.id );
assertEquals( d.id, d.patient.ssn );
d.lastupdate = new Date();
s.flush();
s.clear();
d = (FinancialHistory) s.get( FinancialHistory.class, d.id );
assertNotNull( d.lastupdate );
s.getTransaction().rollback();
s.close();
}
@Override
protected Class<?>[] getMappings() {
return new Class<?>[] {
MedicalHistory.class,
Person.class
Person.class,
FinancialHistory.class
};
}
}

View File

@ -0,0 +1,27 @@
package org.hibernate.test.annotations.derivedidentities.e4.a;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
* @author Emmanuel Bernard
*/
@Entity
public class FinancialHistory {
@Id
String id; // overriding not allowed ... // default join column name is overridden @MapsId
@Temporal(TemporalType.DATE)
Date lastupdate;
@JoinColumn(name = "FK")
@MapsId
@ManyToOne
Person patient;
}

View File

@ -4,6 +4,7 @@ import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;