Skip to content

Commit 77ee7f5

Browse files
committed
HHH-16682 Test and fix dirty checking for @JdbcTypeCode(SqlTypes.JSON) maps
1 parent 3a0cf69 commit 77ee7f5

File tree

3 files changed

+102
-22
lines changed

3 files changed

+102
-22
lines changed

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/CollectionJavaType.java

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@
66
*/
77
package org.hibernate.type.descriptor.java.spi;
88

9+
import java.io.Serializable;
910
import java.lang.reflect.ParameterizedType;
11+
import java.lang.reflect.Type;
12+
import java.util.Map;
1013
import java.util.Objects;
1114

15+
import org.hibernate.SharedSessionContract;
1216
import org.hibernate.collection.spi.CollectionSemantics;
17+
import org.hibernate.collection.spi.MapSemantics;
1318
import org.hibernate.collection.spi.PersistentCollection;
1419
import org.hibernate.type.descriptor.WrapperOptions;
1520
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
1621
import org.hibernate.type.descriptor.java.JavaType;
1722
import org.hibernate.type.descriptor.java.MutabilityPlan;
18-
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
1923
import org.hibernate.type.descriptor.jdbc.JdbcType;
2024
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
2125
import org.hibernate.type.spi.TypeConfiguration;
@@ -52,6 +56,9 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
5256
public JavaType<C> createJavaType(
5357
ParameterizedType parameterizedType,
5458
TypeConfiguration typeConfiguration) {
59+
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
60+
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
61+
final JavaType<Object> valueDescriptor = javaTypeRegistry.resolveDescriptor( actualTypeArguments[actualTypeArguments.length - 1] );
5562
switch ( semantics.getCollectionClassification() ) {
5663
case ARRAY:
5764
case BAG:
@@ -63,14 +70,21 @@ public JavaType<C> createJavaType(
6370
//noinspection unchecked,rawtypes
6471
return new BasicCollectionJavaType(
6572
parameterizedType,
66-
typeConfiguration.getJavaTypeRegistry()
67-
.resolveDescriptor( parameterizedType.getActualTypeArguments()[0] ),
73+
valueDescriptor,
6874
semantics
6975
);
76+
7077
}
7178
// Construct a basic java type that knows its parametrization
72-
//noinspection unchecked
73-
return new UnknownBasicJavaType<>( parameterizedType, (MutabilityPlan<C>) MutableMutabilityPlan.INSTANCE );
79+
//noinspection unchecked,rawtypes
80+
return new UnknownBasicJavaType(
81+
parameterizedType,
82+
new MapMutabilityPlan<>(
83+
(MapSemantics<Map<Object, Object>, Object, Object>) semantics,
84+
javaTypeRegistry.resolveDescriptor( actualTypeArguments[0] ),
85+
valueDescriptor
86+
)
87+
);
7488
}
7589

7690
@Override
@@ -90,30 +104,17 @@ public <X> C wrap(X value, WrapperOptions options) {
90104

91105
@Override
92106
public boolean areEqual(C one, C another) {
93-
// return one == another ||
94-
// (
95-
// one instanceof PersistentCollection &&
96-
// ( (PersistentCollection<?>) one ).wasInitialized() &&
97-
// ( (PersistentCollection<?>) one ).isWrapper( another )
98-
// ) ||
99-
// (
100-
// another instanceof PersistentCollection &&
101-
// ( (PersistentCollection<?>) another ).wasInitialized() &&
102-
// ( (PersistentCollection<?>) another ).isWrapper( one )
103-
// );
104-
105-
106107
if ( one == another ) {
107108
return true;
108109
}
109110

110-
if ( one instanceof PersistentCollection ) {
111-
final PersistentCollection pc = (PersistentCollection) one;
111+
if ( one instanceof PersistentCollection<?> ) {
112+
final PersistentCollection<?> pc = (PersistentCollection<?>) one;
112113
return pc.wasInitialized() && ( pc.isWrapper( another ) || pc.isDirectlyProvidedCollection( another ) );
113114
}
114115

115-
if ( another instanceof PersistentCollection ) {
116-
final PersistentCollection pc = (PersistentCollection) another;
116+
if ( another instanceof PersistentCollection<?> ) {
117+
final PersistentCollection<?> pc = (PersistentCollection<?>) another;
117118
return pc.wasInitialized() && ( pc.isWrapper( one ) || pc.isDirectlyProvidedCollection( one ) );
118119
}
119120

@@ -124,4 +125,50 @@ public boolean areEqual(C one, C another) {
124125
public int extractHashCode(C x) {
125126
throw new UnsupportedOperationException();
126127
}
128+
129+
private static class MapMutabilityPlan<C extends Map<K, V>, K, V> implements MutabilityPlan<C> {
130+
131+
private final MapSemantics<C, K, V> semantics;
132+
private final MutabilityPlan<K> keyPlan;
133+
private final MutabilityPlan<V> valuePlan;
134+
135+
public MapMutabilityPlan(
136+
MapSemantics<C, K, V> semantics,
137+
JavaType<K> keyType,
138+
JavaType<V> valueType) {
139+
this.semantics = semantics;
140+
this.keyPlan = keyType.getMutabilityPlan();
141+
this.valuePlan = valueType.getMutabilityPlan();
142+
}
143+
144+
@Override
145+
public boolean isMutable() {
146+
return true;
147+
}
148+
149+
@Override
150+
public C deepCopy(C value) {
151+
if ( value == null ) {
152+
return null;
153+
}
154+
final C copy = semantics.instantiateRaw( value.size(), null );
155+
156+
for ( Map.Entry<K, V> entry : value.entrySet() ) {
157+
copy.put( keyPlan.deepCopy( entry.getKey() ), valuePlan.deepCopy( entry.getValue() ) );
158+
}
159+
return copy;
160+
}
161+
162+
@Override
163+
public Serializable disassemble(C value, SharedSessionContract session) {
164+
return (Serializable) deepCopy( value );
165+
}
166+
167+
@Override
168+
public C assemble(Serializable cached, SharedSessionContract session) {
169+
//noinspection unchecked
170+
return deepCopy( (C) cached );
171+
}
172+
173+
}
127174
}

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/JsonMappingTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
2727

2828
import org.hibernate.testing.orm.junit.DomainModel;
29+
import org.hibernate.testing.orm.junit.JiraKey;
2930
import org.hibernate.testing.orm.junit.ServiceRegistry;
3031
import org.hibernate.testing.orm.junit.SessionFactory;
3132
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@@ -142,6 +143,23 @@ public void verifyReadWorks(SessionFactoryScope scope) {
142143
);
143144
}
144145

146+
@Test
147+
@JiraKey( "HHH-16682" )
148+
public void verifyDirtyChecking(SessionFactoryScope scope) {
149+
scope.inTransaction(
150+
(session) -> {
151+
EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 );
152+
entityWithJson.stringMap.clear();
153+
}
154+
);
155+
scope.inTransaction(
156+
(session) -> {
157+
EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 );
158+
assertThat( entityWithJson.stringMap.isEmpty(), is( true ) );
159+
}
160+
);
161+
}
162+
145163
@Test
146164
@SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support comparing CLOBs with the = operator")
147165
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA doesn't support comparing LOBs with the = operator")

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.hibernate.testing.orm.domain.gambit.MutableValue;
2828
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
2929
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
30+
import org.hibernate.testing.orm.junit.JiraKey;
3031
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
3132
import org.junit.jupiter.api.AfterEach;
3233
import org.junit.jupiter.api.BeforeEach;
@@ -81,6 +82,20 @@ public void testUpdate() {
8182
);
8283
}
8384

85+
@Test
86+
@JiraKey( "HHH-16682" )
87+
public void testDirtyChecking() {
88+
sessionFactoryScope().inTransaction(
89+
entityManager -> {
90+
JsonHolder jsonHolder = entityManager.find( JsonHolder.class, 1L );
91+
jsonHolder.getAggregate().setTheString( "MyString" );
92+
entityManager.flush();
93+
entityManager.clear();
94+
assertEquals( "MyString", entityManager.find( JsonHolder.class, 1L ).getAggregate().getTheString() );
95+
}
96+
);
97+
}
98+
8499
@Test
85100
public void testFetch() {
86101
sessionFactoryScope().inSession(

0 commit comments

Comments
 (0)