diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index b819e699dd53..9db6a6b4bf23 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -161,6 +161,7 @@ import org.hibernate.mapping.Subclass; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.UnionSubclass; +import org.hibernate.type.ForeignKeyDirection; import static org.hibernate.internal.CoreLogging.messageLogger; @@ -3135,6 +3136,10 @@ private static void bindOneToOne( } } } + ForeignKeyDirection foreignKeyDirection = !BinderHelper.isEmptyAnnotationValue( mappedBy ) + ? ForeignKeyDirection.TO_PARENT + : ForeignKeyDirection.FROM_PARENT; + if ( trueOneToOne || mapToPK || !BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { //is a true one-to-one //FIXME referencedColumnName ignored => ordering may fail. @@ -3150,7 +3155,8 @@ private static void bindOneToOne( optional, cascadeStrategy, joinColumns, - context + context, + foreignKeyDirection ); if ( inSecondPass ) { secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 7a1f3baa7b95..bb22375a7030 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -46,6 +46,7 @@ public class OneToOneSecondPass implements SecondPass { private boolean optional; private String cascadeStrategy; private Ejb3JoinColumn[] joinColumns; + private ForeignKeyDirection foreignKeyDirection; //that suck, we should read that from the property mainly public OneToOneSecondPass( @@ -60,7 +61,8 @@ public OneToOneSecondPass( boolean optional, String cascadeStrategy, Ejb3JoinColumn[] columns, - MetadataBuildingContext buildingContext) { + MetadataBuildingContext buildingContext, + ForeignKeyDirection foreignKeyDirection) { this.ownerEntity = ownerEntity; this.ownerProperty = ownerProperty; this.mappedBy = mappedBy; @@ -73,6 +75,7 @@ public OneToOneSecondPass( this.optional = optional; this.cascadeStrategy = cascadeStrategy; this.joinColumns = columns; + this.foreignKeyDirection = foreignKeyDirection; } //TODO refactor this code, there is a lot of duplication in this method @@ -94,16 +97,8 @@ public void doSecondPass(Map persistentClasses) throws MappingException { if ( !optional ) { value.setConstrained( true ); } - if ( value.isReferenceToPrimaryKey() ) { - value.setForeignKeyType( ForeignKeyDirection.TO_PARENT ); - } - else { - value.setForeignKeyType( - value.isConstrained() - ? ForeignKeyDirection.FROM_PARENT - : ForeignKeyDirection.TO_PARENT - ); - } + value.setForeignKeyType(foreignKeyDirection); + PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); binder.setValue( value ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java index 51a37b4016fe..168e97259fed 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToOne.java @@ -71,7 +71,8 @@ public Type getType() throws MappingException { isLazy(), isUnwrapProxy(), entityName, - propertyName + propertyName, + constrained ); } else { @@ -83,7 +84,8 @@ public Type getType() throws MappingException { isLazy(), isUnwrapProxy(), entityName, - propertyName + propertyName, + constrained ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index 511416994d3a..dfa219cedf72 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -29,9 +29,10 @@ public class OneToOneType extends EntityType { private final ForeignKeyDirection foreignKeyType; private final String propertyName; private final String entityName; + private final boolean constrained; /** - * @deprecated Use {@link #OneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String)} + * @deprecated Use {@link #OneToOneType(TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} * instead. */ @Deprecated @@ -43,8 +44,9 @@ public OneToOneType( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { - this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName ); + String propertyName, + boolean constrained) { + this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, constrained ); } public OneToOneType( @@ -56,11 +58,13 @@ public OneToOneType( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { + String propertyName, + boolean constrained) { super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.foreignKeyType = foreignKeyType; this.propertyName = propertyName; this.entityName = entityName; + this.constrained = constrained; } public OneToOneType(OneToOneType original, String superTypeEntityName) { @@ -68,6 +72,7 @@ public OneToOneType(OneToOneType original, String superTypeEntityName) { this.foreignKeyType = original.foreignKeyType; this.propertyName = original.propertyName; this.entityName = original.entityName; + this.constrained = original.constrained; } @Override @@ -156,7 +161,7 @@ public Object hydrate( @Override protected boolean isNullable() { - return foreignKeyType==ForeignKeyDirection.TO_PARENT; + return !constrained; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java index bd66bfa280d9..466e6a6a5cea 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SpecialOneToOneType.java @@ -27,7 +27,7 @@ public class SpecialOneToOneType extends OneToOneType { /** - * @deprecated Use {@link #SpecialOneToOneType(org.hibernate.type.TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String)} instead. + * @deprecated Use {@link #SpecialOneToOneType(org.hibernate.type.TypeFactory.TypeScope, String, ForeignKeyDirection, boolean, String, boolean, boolean, String, String, boolean)} instead. */ @Deprecated public SpecialOneToOneType( @@ -38,8 +38,9 @@ public SpecialOneToOneType( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { - this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName ); + String propertyName, + boolean constrained) { + this( scope, referencedEntityName, foreignKeyType, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, constrained ); } public SpecialOneToOneType( @@ -51,7 +52,8 @@ public SpecialOneToOneType( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { + String propertyName, + boolean constrained) { super( scope, referencedEntityName, @@ -61,7 +63,8 @@ public SpecialOneToOneType( lazy, unwrapProxy, entityName, - propertyName + propertyName, + constrained ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index d00817a858da..5ebb946a0f59 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -208,10 +208,11 @@ public EntityType oneToOne( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { + String propertyName, + boolean constrained) { return new OneToOneType( typeScope, persistentClass, foreignKeyType, referenceToPrimaryKey, - uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName + uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, constrained ); } @@ -223,10 +224,11 @@ public EntityType specialOneToOne( boolean lazy, boolean unwrapProxy, String entityName, - String propertyName) { + String propertyName, + boolean constrained) { return new SpecialOneToOneType( typeScope, persistentClass, foreignKeyType, referenceToPrimaryKey, - uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName + uniqueKeyPropertyName, lazy, unwrapProxy, entityName, propertyName, constrained ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/OneToOneMergeTest.java b/hibernate-core/src/test/java/org/hibernate/test/ops/OneToOneMergeTest.java new file mode 100644 index 000000000000..396131246c49 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/OneToOneMergeTest.java @@ -0,0 +1,136 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.ops; + +import java.io.Serializable; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertNotNull; + +/** + * + * @author localEvg + */ +@TestForIssue( jiraKey = "HHH-12436" ) +public class OneToOneMergeTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ + Prima.class, + Secunda.class + }; + } + + @Test + public void testMerge() throws Exception { + + Long primaId = doInJPA( this::entityManagerFactory, entityManager -> { + Prima prima = new Prima(); + prima.setOptionalData(null); + + entityManager.persist(prima); + + return prima.getId(); + } ); + + assertNotNull(primaId); + + doInJPA( this::entityManagerFactory, entityManager -> { + Prima prima = entityManager.find( Prima.class, primaId ); + + Secunda sec = new Secunda(); + sec.setParent(prima); + prima.setOptionalData(sec); + + Prima mergedPrima = entityManager.merge(prima); + + assertNotNull(mergedPrima); + } ); + + } + + @Entity(name = "Prima") + public static class Prima implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + //@PrimaryKeyJoinColumn + @OneToOne(mappedBy = "parent", optional = true , cascade = CascadeType.ALL) + private Secunda optionalData; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Secunda getOptionalData() { + return optionalData; + } + + public void setOptionalData(Secunda optionalData) { + this.optionalData = optionalData; + } + + } + + @Entity(name = "Secunda") + public static class Secunda implements Serializable { + + @Id + @Column(name = "id", nullable = false) + private Long id; + + @MapsId + @OneToOne(optional = false) + @JoinColumn(name = "id", nullable = false) + private Prima parent; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Prima getParent() { + return parent; + } + + public void setParent(Prima parent) { + this.parent = parent; + } + + } +}