Skip to content

Commit 471ebd6

Browse files
imunicbeikov
authored andcommitted
HHH-17840 Fix inconsistency of read/write null JsonNode/JsonValue
1 parent 43e0914 commit 471ebd6

File tree

17 files changed

+234
-79
lines changed

17 files changed

+234
-79
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
7171
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
7272
import org.hibernate.type.descriptor.jdbc.EnumJdbcType;
73-
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
7473
import org.hibernate.type.descriptor.jdbc.JdbcType;
7574
import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType;
7675
import org.hibernate.type.descriptor.jdbc.TimeAsTimestampWithTimeZoneJdbcType;
@@ -294,7 +293,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
294293
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
295294
}
296295
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
297-
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE );
296+
jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonJdbcType.INSTANCE );
298297
}
299298
jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE );
300299
jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE );

hibernate-core/hibernate-core.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ dependencies {
6666
testRuntimeOnly libs.byteBuddy
6767
testRuntimeOnly testLibs.weld
6868
testRuntimeOnly testLibs.wildFlyTxnClient
69-
testRuntimeOnly jakartaLibs.jsonb
70-
testRuntimeOnly libs.jackson
69+
testImplementation jakartaLibs.jsonb
70+
testImplementation libs.jackson
7171
testRuntimeOnly libs.jacksonXml
7272
testRuntimeOnly libs.jacksonJsr310
7373

hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
6767
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
6868
import org.hibernate.type.descriptor.jdbc.EnumJdbcType;
69-
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
7069
import org.hibernate.type.descriptor.jdbc.JdbcType;
7170
import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType;
7271
import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
@@ -245,7 +244,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
245244
jdbcTypeRegistry.addDescriptor( TimestampUtcAsInstantJdbcType.INSTANCE );
246245
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
247246
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
248-
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE );
247+
jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonJdbcType.INSTANCE );
249248
jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE );
250249
jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE );
251250
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.dialect;
8+
9+
import java.nio.charset.StandardCharsets;
10+
import java.sql.CallableStatement;
11+
import java.sql.PreparedStatement;
12+
import java.sql.SQLException;
13+
14+
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
15+
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
16+
import org.hibernate.type.descriptor.ValueBinder;
17+
import org.hibernate.type.descriptor.WrapperOptions;
18+
import org.hibernate.type.descriptor.java.JavaType;
19+
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
20+
import org.hibernate.type.descriptor.jdbc.BasicBinder;
21+
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
22+
23+
/**
24+
* H2 requires binding JSON via {@code setBytes} methods.
25+
*/
26+
public class H2JsonJdbcType extends JsonJdbcType {
27+
/**
28+
* Singleton access
29+
*/
30+
public static final H2JsonJdbcType INSTANCE = new H2JsonJdbcType( null );
31+
32+
protected H2JsonJdbcType(EmbeddableMappingType embeddableMappingType) {
33+
super( embeddableMappingType );
34+
}
35+
36+
@Override
37+
public String toString() {
38+
return "FormatJsonJdbcType";
39+
}
40+
41+
@Override
42+
public AggregateJdbcType resolveAggregateJdbcType(
43+
EmbeddableMappingType mappingType,
44+
String sqlType,
45+
RuntimeModelCreationContext creationContext) {
46+
return new H2JsonJdbcType( mappingType );
47+
}
48+
49+
@Override
50+
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
51+
return new BasicBinder<>( javaType, this ) {
52+
@Override
53+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
54+
throws SQLException {
55+
final String json = ( (H2JsonJdbcType) getJdbcType() ).toString( value, getJavaType(), options );
56+
st.setBytes( index, json.getBytes( StandardCharsets.UTF_8 ) );
57+
}
58+
59+
@Override
60+
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
61+
throws SQLException {
62+
final String json = ( (H2JsonJdbcType) getJdbcType() ).toString( value, getJavaType(), options );
63+
st.setBytes( name, json.getBytes( StandardCharsets.UTF_8 ) );
64+
}
65+
};
66+
}
67+
}

hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.hibernate.query.sqm.tree.SqmCopyContext;
1414
import org.hibernate.type.descriptor.java.JavaType;
1515

16+
import org.checkerframework.checker.nullness.qual.Nullable;
17+
1618
/**
1719
* Represents a literal value in the sqm, e.g.<ul>
1820
* <li>1</li>
@@ -75,13 +77,18 @@ public void appendHqlString(StringBuilder sb) {
7577
appendHqlString( sb, getJavaTypeDescriptor(), getLiteralValue() );
7678
}
7779

78-
public static <T> void appendHqlString(StringBuilder sb, JavaType<T> javaType, T value) {
79-
final String string = javaType.toString( value );
80-
if ( javaType.getJavaTypeClass() == String.class ) {
81-
QueryLiteralHelper.appendStringLiteral( sb, string );
80+
public static <T> void appendHqlString(StringBuilder sb, JavaType<T> javaType, @Nullable T value) {
81+
if ( value == null ) {
82+
sb.append( "null" );
8283
}
8384
else {
84-
sb.append( string );
85+
final String string = javaType.toString( value );
86+
if ( javaType.getJavaTypeClass() == String.class ) {
87+
QueryLiteralHelper.appendStringLiteral( sb, string );
88+
}
89+
else {
90+
sb.append( string );
91+
}
8592
}
8693
}
8794

hibernate-core/src/main/java/org/hibernate/type/EnumType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ public T fromStringValue(CharSequence sequence) {
296296
@Override @SuppressWarnings("unchecked")
297297
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
298298
verifyConfigured();
299-
return enumJavaType.toString( (T) value );
299+
return enumJavaType.extractLoggableRepresentation( (T) value );
300300
}
301301

302302
public boolean isOrdinal() {

hibernate-core/src/main/java/org/hibernate/type/Type.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ default String getReturnedClassName() {
154154
*
155155
* @throws HibernateException A problem occurred performing the comparison
156156
*/
157-
boolean isSame(Object x, Object y) throws HibernateException;
157+
boolean isSame(@Nullable Object x, @Nullable Object y) throws HibernateException;
158158

159159
/**
160160
* Compare two instances of the class mapped by this type for persistence "equality",
@@ -173,7 +173,7 @@ default String getReturnedClassName() {
173173
*
174174
* @throws HibernateException A problem occurred performing the comparison
175175
*/
176-
boolean isEqual(Object x, Object y) throws HibernateException;
176+
boolean isEqual(@Nullable Object x, @Nullable Object y) throws HibernateException;
177177

178178
/**
179179
* Compare two instances of the class mapped by this type for persistence "equality",
@@ -193,7 +193,7 @@ default String getReturnedClassName() {
193193
*
194194
* @throws HibernateException A problem occurred performing the comparison
195195
*/
196-
boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException;
196+
boolean isEqual(@Nullable Object x, @Nullable Object y, SessionFactoryImplementor factory) throws HibernateException;
197197

198198
/**
199199
* Get a hash code, consistent with persistence "equality". For most types this could
@@ -229,9 +229,9 @@ default String getReturnedClassName() {
229229
*
230230
* @see java.util.Comparator#compare(Object, Object)
231231
*/
232-
int compare(Object x, Object y);
232+
int compare(@Nullable Object x, @Nullable Object y);
233233

234-
int compare(Object x, Object y, SessionFactoryImplementor sessionFactory);
234+
int compare(@Nullable Object x, @Nullable Object y, SessionFactoryImplementor sessionFactory);
235235

236236
/**
237237
* Should the parent be considered dirty, given both the old and current value?
@@ -244,7 +244,7 @@ default String getReturnedClassName() {
244244
*
245245
* @throws HibernateException A problem occurred performing the checking
246246
*/
247-
boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException;
247+
boolean isDirty(@Nullable Object old, @Nullable Object current, SharedSessionContractImplementor session) throws HibernateException;
248248

249249
/**
250250
* Should the parent be considered dirty, given both the old and current value?
@@ -258,7 +258,7 @@ default String getReturnedClassName() {
258258
*
259259
* @throws HibernateException A problem occurred performing the checking
260260
*/
261-
boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session)
261+
boolean isDirty(@Nullable Object oldState, @Nullable Object currentState, boolean[] checkable, SharedSessionContractImplementor session)
262262
throws HibernateException;
263263

264264
/**
@@ -277,8 +277,8 @@ boolean isDirty(Object oldState, Object currentState, boolean[] checkable, Share
277277
* @throws HibernateException A problem occurred performing the checking
278278
*/
279279
boolean isModified(
280-
Object dbState,
281-
Object currentState,
280+
@Nullable Object dbState,
281+
@Nullable Object currentState,
282282
boolean[] checkable,
283283
SharedSessionContractImplementor session)
284284
throws HibernateException;
@@ -300,7 +300,7 @@ boolean isModified(
300300
*/
301301
void nullSafeSet(
302302
PreparedStatement st,
303-
Object value,
303+
@Nullable Object value,
304304
int index,
305305
boolean[] settable,
306306
SharedSessionContractImplementor session)
@@ -320,7 +320,7 @@ void nullSafeSet(
320320
* @throws HibernateException An error from Hibernate
321321
* @throws SQLException An error from the JDBC driver
322322
*/
323-
void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
323+
void nullSafeSet(PreparedStatement st, @Nullable Object value, int index, SharedSessionContractImplementor session)
324324
throws HibernateException, SQLException;
325325

326326
/**
@@ -353,7 +353,7 @@ String toLoggableString(@Nullable Object value, SessionFactoryImplementor factor
353353
*
354354
* @throws HibernateException An error from Hibernate
355355
*/
356-
Object deepCopy(Object value, SessionFactoryImplementor factory)
356+
@Nullable Object deepCopy(@Nullable Object value, SessionFactoryImplementor factory)
357357
throws HibernateException;
358358

359359
/**
@@ -392,7 +392,7 @@ Object deepCopy(Object value, SessionFactoryImplementor factory)
392392
*
393393
* @throws HibernateException An error from Hibernate
394394
*/
395-
default Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException {
395+
default @Nullable Serializable disassemble(@Nullable Object value, SessionFactoryImplementor sessionFactory) throws HibernateException {
396396
return disassemble( value, null, null );
397397
}
398398

@@ -410,7 +410,7 @@ default Serializable disassemble(Object value, SessionFactoryImplementor session
410410
*
411411
* @throws HibernateException An error from Hibernate
412412
*/
413-
Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException;
413+
@Nullable Serializable disassemble(@Nullable Object value, @Nullable SharedSessionContractImplementor session, @Nullable Object owner) throws HibernateException;
414414

415415
/**
416416
* Reconstruct the object from its disassembled state. This function is the inverse of
@@ -424,15 +424,17 @@ default Serializable disassemble(Object value, SessionFactoryImplementor session
424424
*
425425
* @throws HibernateException An error from Hibernate
426426
*/
427-
Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException;
427+
@Nullable Object assemble(@Nullable Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException;
428428

429429
/**
430430
* Called before assembling a query result set from the query cache, to allow batch
431431
* fetching of entities missing from the second-level cache.
432432
*
433433
* @param cached The key
434434
* @param session The originating session
435+
* @deprecated Is not called anymore
435436
*/
437+
@Deprecated(forRemoval = true, since = "6.6")
436438
void beforeAssemble(Serializable cached, SharedSessionContractImplementor session);
437439

438440
/**
@@ -452,9 +454,9 @@ default Serializable disassemble(Object value, SessionFactoryImplementor session
452454
*
453455
* @throws HibernateException An error from Hibernate
454456
*/
455-
Object replace(
456-
Object original,
457-
Object target,
457+
@Nullable Object replace(
458+
@Nullable Object original,
459+
@Nullable Object target,
458460
SharedSessionContractImplementor session,
459461
Object owner,
460462
Map<Object, Object> copyCache) throws HibernateException;
@@ -477,9 +479,9 @@ Object replace(
477479
*
478480
* @throws HibernateException An error from Hibernate
479481
*/
480-
Object replace(
481-
Object original,
482-
Object target,
482+
@Nullable Object replace(
483+
@Nullable Object original,
484+
@Nullable Object target,
483485
SharedSessionContractImplementor session,
484486
Object owner,
485487
Map<Object, Object> copyCache,
@@ -494,5 +496,5 @@ Object replace(
494496
*
495497
* @return array indicating column nullness for a value instance
496498
*/
497-
boolean[] toColumnNullness(Object value, Mapping mapping);
499+
boolean[] toColumnNullness(@Nullable Object value, Mapping mapping);
498500
}

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
2727
import org.hibernate.type.spi.TypeConfiguration;
2828

29+
import org.checkerframework.checker.nullness.qual.Nullable;
30+
2931
/**
3032
* Descriptor for the Java side of a value mapping. A {@code JavaType} is always
3133
* coupled with a {@link JdbcType} to describe the typing aspects of an attribute
@@ -228,12 +230,12 @@ default boolean areEqual(T one, T another) {
228230
*
229231
* @return The loggable representation
230232
*/
231-
default String extractLoggableRepresentation(T value) {
232-
return toString( value );
233+
default String extractLoggableRepresentation(@Nullable T value) {
234+
return value == null ? "null" : toString( value );
233235
}
234236

235237
default String toString(T value) {
236-
return value == null ? "null" : value.toString();
238+
return value.toString();
237239
}
238240

239241
T fromString(CharSequence string);

hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutabilityPlan.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import org.hibernate.SharedSessionContract;
1212

13+
import org.checkerframework.checker.nullness.qual.Nullable;
14+
1315
/**
1416
* Describes the mutability aspects of a given Java type.
1517
* <p>
@@ -65,7 +67,7 @@ public interface MutabilityPlan<T> extends Serializable {
6567
*
6668
* @return The deep copy.
6769
*/
68-
T deepCopy(T value);
70+
@Nullable T deepCopy(@Nullable T value);
6971

7072
/**
7173
* Return a disassembled representation of the value.
@@ -76,7 +78,7 @@ public interface MutabilityPlan<T> extends Serializable {
7678
*
7779
* @see #assemble
7880
*/
79-
Serializable disassemble(T value, SharedSessionContract session);
81+
@Nullable Serializable disassemble(@Nullable T value, SharedSessionContract session);
8082

8183
/**
8284
* Assemble a previously {@linkplain #disassemble disassembled} value.
@@ -87,5 +89,5 @@ public interface MutabilityPlan<T> extends Serializable {
8789
*
8890
* @see #disassemble
8991
*/
90-
T assemble(Serializable cached, SharedSessionContract session);
92+
@Nullable T assemble(@Nullable Serializable cached, SharedSessionContract session);
9193
}

0 commit comments

Comments
 (0)