diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Child.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Child.java new file mode 100644 index 0000000000..1539c2bb02 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Child.java @@ -0,0 +1,39 @@ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.merge; + +import org.hibernate.annotations.CompositeType; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Child { + + @Id + @GeneratedValue + protected Long id; + + @Embedded + @AttributeOverride(name = "id", column = @Column(name = "value_id")) + @AttributeOverride(name = "hash", column = @Column(name = "value_hash")) + @CompositeType(MyCompositeValueType.class) + private MyCompositeValue compositeValue = new MyCompositeValue(); + + public Child() { + } + + public Child(MyCompositeValue compositeValue) { + this.compositeValue = compositeValue; + } + + public Long getId() { + return id; + } + + public MyCompositeValue getCompositeValue() { + return compositeValue; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MergeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MergeTest.java new file mode 100644 index 0000000000..126b8bac2d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MergeTest.java @@ -0,0 +1,50 @@ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.merge; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { Parent.class, Child.class } +) +@SessionFactory +@TestForIssue(jiraKey = "HHH-16015") +public class MergeTest { + + @Test + public void testMerge(SessionFactoryScope scope) { + final Parent parent = new Parent( 1l, "Lio" ); + scope.inTransaction( + session -> { + Child child = new Child( new MyCompositeValue( 2l, "initial value" ) ); + parent.setChild( child ); + session.persist( parent ); + } + ); + + scope.inTransaction( + session -> { + Child child = new Child( new MyCompositeValue( 1l, "updated value" ) ); + parent.setChild( child ); + + session.merge( parent ); + } + ); + + scope.inTransaction( + session -> { + Parent lio = session.get( Parent.class, 1 ); + Child child = lio.getChild(); + + assertThat( child ).isNotNull(); + MyCompositeValue compositeValue = child.getCompositeValue(); + assertThat( compositeValue ).isNotNull(); + assertThat( compositeValue.stringValue() ).isEqualTo( "updated value" ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValue.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValue.java new file mode 100644 index 0000000000..4ae3b2ca74 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValue.java @@ -0,0 +1,23 @@ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.merge; + +public class MyCompositeValue { + protected Long longValue; + protected String stringValue; + + public MyCompositeValue() { + } + + public MyCompositeValue(Long longValue, String stringValue) { + this.longValue = longValue; + this.stringValue = stringValue; + } + + public Long longValue() { + return longValue; + } + + public String stringValue() { + return stringValue; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValueType.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValueType.java new file mode 100644 index 0000000000..8f27028816 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/MyCompositeValueType.java @@ -0,0 +1,84 @@ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.merge; + +import java.io.Serializable; +import java.util.Objects; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.spi.ValueAccess; +import org.hibernate.usertype.CompositeUserType; + +public class MyCompositeValueType implements CompositeUserType { + public static class EmbeddableMapper { + Long longValue; + String stringValue; + } + + @Override + public Object getPropertyValue(MyCompositeValue component, int property) throws HibernateException { + switch ( property ) { + case 0: + return component.longValue(); + case 1: + return component.stringValue(); + default: + return null; + } + } + + @Override + public MyCompositeValue instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) { + final Long id = values.getValue( 0, Long.class ); + final String hash = values.getValue( 1, String.class ); + return new MyCompositeValue( id, hash ); + } + + @Override + public Class embeddable() { + return EmbeddableMapper.class; + } + + @Override + public Class returnedClass() { + return MyCompositeValue.class; + } + + @Override + public boolean equals(MyCompositeValue x, MyCompositeValue y) { + return Objects.equals( x, y ); + } + + @Override + public int hashCode(MyCompositeValue x) { + return Objects.hashCode( x ); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public MyCompositeValue deepCopy(MyCompositeValue value) { + if ( value == null ) { + return null; + } + return new MyCompositeValue( value.longValue(), value.stringValue() ); + } + + @Override + public Serializable disassemble(MyCompositeValue value) { + return new Object[] { value.longValue(), value.stringValue() }; + } + + @Override + public MyCompositeValue assemble(Serializable cached, Object owner) { + final Object[] parts = (Object[]) cached; + return new MyCompositeValue( (Long) parts[0], (String) parts[1] ); + } + + @Override + public MyCompositeValue replace(MyCompositeValue detached, MyCompositeValue managed, Object owner) { + return deepCopy( detached ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Parent.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Parent.java new file mode 100644 index 0000000000..54cb13a2e6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/merge/Parent.java @@ -0,0 +1,40 @@ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.merge; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +@Entity +public class Parent { + + @Id + protected Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private Child child; + + public Parent() { + } + + public Parent(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return this.id; + } + + public Child getChild() { + return this.child; + } + + public void setChild(Child child) { + this.child = child; + } +}