From b67a0bad326c707be649391cb48265eb444a593b Mon Sep 17 00:00:00 2001 From: The-Huginn Date: Wed, 8 Nov 2023 14:10:12 +0100 Subject: [PATCH] [HHH-17294] DeepCopy non-Embedded JSON or XML JdbcTypCode attribute using FormatMapper --- .../boot/model/internal/BasicValueBinder.java | 2 + .../org/hibernate/mapping/BasicValue.java | 62 +++++++-- .../type/AbstractStandardBasicType.java | 5 +- .../java/spi/FormatMapperBasedJavaType.java | 122 ++++++++++++++++++ .../descriptor/java/spi/JavaTypeRegistry.java | 32 +++-- .../descriptor/java/spi/JsonJavaType.java | 43 ++++++ .../descriptor/java/spi/RegistryHelper.java | 6 +- .../type/descriptor/java/spi/XmlJavaType.java | 43 ++++++ .../test/mapping/embeddable/Aggregate.java | 7 + ...nAggregateTest.java => AggregateTest.java} | 76 ++++++++--- 10 files changed, 351 insertions(+), 47 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/FormatMapperBasedJavaType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JsonJavaType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/XmlJavaType.java rename hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/{JsonAggregateTest.java => AggregateTest.java} (51%) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index 0ae1c658c2..d13203b587 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -72,6 +72,8 @@ import org.hibernate.type.descriptor.java.Immutability; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.java.spi.JsonJavaType; +import org.hibernate.type.descriptor.java.spi.XmlJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 37b97c3f3c..cc805c0f34 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -53,6 +53,7 @@ import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.type.BasicType; import org.hibernate.type.CustomType; import org.hibernate.type.NumericBooleanConverter; +import org.hibernate.type.SqlTypes; import org.hibernate.type.TrueFalseConverter; import org.hibernate.type.Type; import org.hibernate.type.WrapperArrayHandling; @@ -62,6 +63,11 @@ import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.java.spi.FormatMapperBasedJavaType; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; +import org.hibernate.type.descriptor.java.spi.JsonJavaType; +import org.hibernate.type.descriptor.java.spi.RegistryHelper; +import org.hibernate.type.descriptor.java.spi.XmlJavaType; import org.hibernate.type.descriptor.jdbc.BooleanJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; @@ -697,15 +703,15 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol private JavaType determineJavaType(JavaType explicitJavaType) { JavaType javaType = explicitJavaType; - - if ( javaType == null ) { - if ( implicitJavaTypeAccess != null ) { - final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() ); - if ( implicitJtd != null ) { - javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd ); - } - } - } +// +// if ( javaType == null ) { +// if ( implicitJavaTypeAccess != null ) { +// final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() ); +// if ( implicitJtd != null ) { +// javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd ); +// } +// } +// } if ( javaType == null ) { final JavaType reflectedJtd = determineReflectedJavaType(); @@ -720,11 +726,12 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol private JavaType determineReflectedJavaType() { final java.lang.reflect.Type impliedJavaType; + final TypeConfiguration typeConfiguration = getTypeConfiguration(); if ( resolvedJavaType != null ) { impliedJavaType = resolvedJavaType; } else if ( implicitJavaTypeAccess != null ) { - impliedJavaType = implicitJavaTypeAccess.apply( getTypeConfiguration() ); + impliedJavaType = implicitJavaTypeAccess.apply( typeConfiguration ); } else if ( ownerName != null && propertyName != null ) { impliedJavaType = ReflectHelper.reflectedPropertyType( @@ -743,7 +750,40 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol return null; } - return getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( impliedJavaType ); + final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); + final JavaType javaType = javaTypeRegistry.findDescriptor( impliedJavaType ); + final MutabilityPlan explicitMutabilityPlan = explicitMutabilityPlanAccess != null + ? explicitMutabilityPlanAccess.apply( typeConfiguration ) + : null; + final MutabilityPlan determinedMutabilityPlan = explicitMutabilityPlan != null + ? explicitMutabilityPlan + : RegistryHelper.INSTANCE.determineMutabilityPlan( impliedJavaType, typeConfiguration ); + if ( javaType == null ) { + if ( jdbcTypeCode != null ) { + // Construct special JavaType instances for JSON/XML types which can report recommended JDBC types + // and implement toString/fromString as well as copying based on FormatMapper operations + switch ( jdbcTypeCode ) { + case SqlTypes.JSON: + final JavaType jsonJavaType = new JsonJavaType<>( + impliedJavaType, + determinedMutabilityPlan, + typeConfiguration + ); + javaTypeRegistry.addDescriptor( jsonJavaType ); + return jsonJavaType; + case SqlTypes.SQLXML: + final JavaType xmlJavaType = new XmlJavaType<>( + impliedJavaType, + determinedMutabilityPlan, + typeConfiguration + ); + javaTypeRegistry.addDescriptor( xmlJavaType ); + return xmlJavaType; + } + } + return javaTypeRegistry.resolveDescriptor( impliedJavaType ); + } + return javaType; } private static Resolution interpretExplicitlyNamedType( diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index f5f2d7ebf0..a95a293e74 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -30,6 +30,7 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.AbstractClassJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.java.MutableMutabilityPlan; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -242,7 +243,9 @@ public abstract class AbstractStandardBasicType } protected final boolean isDirty(Object old, Object current) { - return !isSame( old, current ); + // MutableMutabilityPlan.INSTANCE is a special plan for which we always have to assume the value is dirty, + // because we can't actually copy a value, but have no knowledge about the mutability of the java type + return getMutabilityPlan() == MutableMutabilityPlan.INSTANCE || !isSame( old, current ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/FormatMapperBasedJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/FormatMapperBasedJavaType.java new file mode 100644 index 0000000000..3efb550241 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/FormatMapperBasedJavaType.java @@ -0,0 +1,122 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.type.descriptor.java.spi; + +import java.io.Serializable; +import java.lang.reflect.Type; + +import org.hibernate.Incubating; +import org.hibernate.SharedSessionContract; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.AbstractJavaType; +import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Java type for {@link FormatMapper} based types i.e. {@link org.hibernate.type.SqlTypes#JSON} + * or {@link org.hibernate.type.SqlTypes#SQLXML} mapped types. + * + * @author Christian Beikov + */ +@Incubating +public abstract class FormatMapperBasedJavaType extends AbstractJavaType implements MutabilityPlan { + + private final TypeConfiguration typeConfiguration; + + public FormatMapperBasedJavaType( + Type type, + MutabilityPlan mutabilityPlan, + TypeConfiguration typeConfiguration) { + super( type, mutabilityPlan ); + this.typeConfiguration = typeConfiguration; + } + + protected abstract FormatMapper getFormatMapper(TypeConfiguration typeConfiguration); + + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { + throw new JdbcTypeRecommendationException( + "Could not determine recommended JdbcType for Java type '" + getJavaType().getTypeName() + "'" + ); + } + + @Override + public String toString(T value) { + return getFormatMapper( typeConfiguration ).toString( + value, + this, + typeConfiguration.getSessionFactory().getWrapperOptions() + ); + } + + @Override + public T fromString(CharSequence string) { + return getFormatMapper( typeConfiguration ).fromString( + string, + this, + typeConfiguration.getSessionFactory().getWrapperOptions() + ); + } + + @Override + public X unwrap(T value, Class type, WrapperOptions options) { + if ( type.isAssignableFrom( getJavaTypeClass() ) ) { + //noinspection unchecked + return (X) value; + } + else if ( type == String.class ) { + //noinspection unchecked + return (X) getFormatMapper( typeConfiguration ).toString( value, this, options ); + } + throw new UnsupportedOperationException( + "Unwrap strategy not known for this Java type : " + getJavaType().getTypeName() + ); + } + + @Override + public T wrap(X value, WrapperOptions options) { + if ( getJavaTypeClass().isInstance( value ) ) { + //noinspection unchecked + return (T) value; + } + else if ( value instanceof String ) { + return getFormatMapper( typeConfiguration ).fromString( (String) value, this, options ); + } + throw new UnsupportedOperationException( + "Wrap strategy not known for this Java type : " + getJavaType().getTypeName() + ); + } + + @Override + public MutabilityPlan getMutabilityPlan() { + final MutabilityPlan mutabilityPlan = super.getMutabilityPlan(); + return mutabilityPlan == null ? this : mutabilityPlan; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public T deepCopy(T value) { + return fromString( toString( value ) ); + } + + @Override + public Serializable disassemble(T value, SharedSessionContract session) { + return toString( value ); + } + + @Override + public T assemble(Serializable cached, SharedSessionContract session) { + return fromString( (CharSequence) cached ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java index 1e77dad657..37870323b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java @@ -10,6 +10,7 @@ import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; @@ -110,6 +111,22 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial } public JavaType resolveDescriptor(Type javaType) { + return resolveDescriptor( javaType, (elementJavaType, typeConfiguration) -> { + final MutabilityPlan determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan( + elementJavaType, + typeConfiguration + ); + if ( determinedPlan != null ) { + return determinedPlan; + } + + return MutableMutabilityPlan.INSTANCE; + } ); + } + + public JavaType resolveDescriptor( + Type javaType, + BiFunction> mutabilityPlanCreator) { return resolveDescriptor( javaType, () -> { @@ -131,21 +148,10 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial elementTypeDescriptor = null; } if ( elementTypeDescriptor == null ) { + //noinspection unchecked elementTypeDescriptor = RegistryHelper.INSTANCE.createTypeDescriptor( elementJavaType, - () -> { - final MutabilityPlan determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan( - elementJavaType, - typeConfiguration - ); - if ( determinedPlan != null ) { - return determinedPlan; - } - - //noinspection unchecked - return (MutabilityPlan) MutableMutabilityPlan.INSTANCE; - - }, + () -> (MutabilityPlan) mutabilityPlanCreator.apply( elementJavaType, typeConfiguration ), typeConfiguration ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JsonJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JsonJavaType.java new file mode 100644 index 0000000000..1477bcd867 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JsonJavaType.java @@ -0,0 +1,43 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.type.descriptor.java.spi; + +import java.lang.reflect.Type; + +import org.hibernate.Incubating; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.spi.TypeConfiguration; + +@Incubating +public class JsonJavaType extends FormatMapperBasedJavaType { + + public JsonJavaType( + Type type, + MutabilityPlan mutabilityPlan, + TypeConfiguration typeConfiguration) { + super( type, mutabilityPlan, typeConfiguration ); + } + + @Override + protected FormatMapper getFormatMapper(TypeConfiguration typeConfiguration) { + return typeConfiguration.getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + } + + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { + return context.getJdbcType( SqlTypes.JSON ); + } + + @Override + public String toString() { + return "JsonJavaType(" + getJavaType().getTypeName() + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java index c22b8baaa1..1cf2f2fcd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/RegistryHelper.java @@ -62,11 +62,7 @@ public class RegistryHelper { return typeConfiguration.createMutabilityPlan( annotation.value() ); } - if ( javaTypeClass.isEnum() ) { - return ImmutableMutabilityPlan.instance(); - } - - if ( javaTypeClass.isPrimitive() ) { + if ( javaTypeClass.isEnum() || javaTypeClass.isPrimitive() || ReflectHelper.isRecord( javaTypeClass ) ) { return ImmutableMutabilityPlan.instance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/XmlJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/XmlJavaType.java new file mode 100644 index 0000000000..bfbd27b63f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/XmlJavaType.java @@ -0,0 +1,43 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.type.descriptor.java.spi; + +import java.lang.reflect.Type; + +import org.hibernate.Incubating; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.java.MutabilityPlan; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.spi.TypeConfiguration; + +@Incubating +public class XmlJavaType extends FormatMapperBasedJavaType { + + public XmlJavaType( + Type type, + MutabilityPlan mutabilityPlan, + TypeConfiguration typeConfiguration) { + super( type, mutabilityPlan, typeConfiguration ); + } + + @Override + protected FormatMapper getFormatMapper(TypeConfiguration typeConfiguration) { + return typeConfiguration.getSessionFactory().getFastSessionServices().getXmlFormatMapper(); + } + + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) { + return context.getJdbcType( SqlTypes.SQLXML ); + } + + @Override + public String toString() { + return "XmlJavaType(" + getJavaType().getTypeName() + ")"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java index 184e0cfefd..cf4b2250e4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java @@ -466,4 +466,11 @@ public class Aggregate { } return Objects.equals( mutableValue, that.mutableValue ); } + + @Override + public int hashCode() { + int result = Objects.hash(theBoolean, theNumericBoolean, theStringBoolean, theString, theInteger, theInt, theDouble, theUrl, theClob, theDate, theTime, theTimestamp, theInstant, theUuid, gender, convertedGender, ordinalGender, theDuration, theLocalDateTime, theLocalDate, theLocalTime, theZonedDateTime, theOffsetDateTime, mutableValue); + result = 31 * result + Arrays.hashCode(theBinary); + return result; + } } \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonAggregateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/AggregateTest.java similarity index 51% rename from hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonAggregateTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/AggregateTest.java index b6f55c555f..6e1d7ccfa5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonAggregateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/AggregateTest.java @@ -12,23 +12,24 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.JiraKey; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.type.SqlTypes; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -//@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonAggregate.class) -public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest { +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonAggregate.class) +public class AggregateTest extends BaseSessionFactoryFunctionalTest { @Override protected Class[] getAnnotatedClasses() { return new Class[] { - JsonHolder.class + JsonHolder.class, XmlHolder.class }; } @@ -37,6 +38,7 @@ public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest { inTransaction( session -> { session.persist( new JsonHolder( 1L, Aggregate.createAggregate2() ) ); + session.persist( new XmlHolder( 1L, Aggregate.createAggregate2() ) ); } ); } @@ -46,27 +48,41 @@ public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest { inTransaction( session -> { session.createMutationQuery( "delete from JsonHolder h" ).executeUpdate(); + session.createMutationQuery( "delete from XmlHolder h" ).executeUpdate(); } ); } @Test @JiraKey("HHH-17294") - public void testDirtyChecking() { + public void testDirtyCheckingJsonAggregate() { sessionFactoryScope().inTransaction( entityManager -> { - JsonHolder jsonHolder = entityManager.find( JsonHolder.class, 1L ); - assertEquals("String 'abc'", jsonHolder.getAggregate().getTheString()); - jsonHolder.getAggregate().setTheString( "MyString" ); + JsonHolder aggregateHolder = entityManager.find( JsonHolder.class, 1L ); + Assertions.assertEquals("String 'abc'", aggregateHolder.getAggregate().getTheString()); + aggregateHolder.getAggregate().setTheString( "MyString" ); entityManager.flush(); entityManager.clear(); - // Fails, when it should pass - assertEquals( "String 'MyString'", entityManager.find( JsonHolder.class, 1L ).getAggregate().getTheString() ); + Assertions.assertEquals( "MyString", entityManager.find( JsonHolder.class, 1L ).getAggregate().getTheString() ); + } + ); + } + + @Test + @JiraKey("HHH-17294") + public void testDirtyCheckingXmlAggregate() { + sessionFactoryScope().inTransaction( + entityManager -> { + XmlHolder aggregateHolder = entityManager.find( XmlHolder.class, 1L ); + Assertions.assertEquals("String 'abc'", aggregateHolder.getAggregate().getTheString()); + aggregateHolder.getAggregate().setTheString( "MyString" ); + entityManager.flush(); + entityManager.clear(); + Assertions.assertEquals( "MyString", entityManager.find( XmlHolder.class, 1L ).getAggregate().getTheString() ); } ); } - //tag::json-type-mapping-example[] @Entity(name = "JsonHolder") public static class JsonHolder { @@ -75,9 +91,6 @@ public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest { @JdbcTypeCode(SqlTypes.JSON) private Aggregate aggregate; - //end::json-type-mapping-example[] - //Getters and setters are omitted for brevity - public JsonHolder() { } @@ -101,10 +114,39 @@ public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest { public void setAggregate(Aggregate aggregate) { this.aggregate = aggregate; } - - //tag::json-type-mapping-example[] } - //end::json-type-mapping-example[] + @Entity(name = "XmlHolder") + public static class XmlHolder { + + @Id + private Long id; + @JdbcTypeCode(SqlTypes.SQLXML) + private Aggregate aggregate; + + public XmlHolder() { + } + + public XmlHolder(Long id, Aggregate aggregate) { + this.id = id; + this.aggregate = aggregate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Aggregate getAggregate() { + return aggregate; + } + + public void setAggregate(Aggregate aggregate) { + this.aggregate = aggregate; + } + } }