HHH-17840 Fix inconsistency of read/write null JsonNode/JsonValue

This commit is contained in:
imunic 2024-03-12 21:55:09 +01:00 committed by Christian Beikov
parent a882fbdf0c
commit c5d5bc1922
17 changed files with 235 additions and 79 deletions

View File

@ -70,7 +70,6 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLe
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.EnumJdbcType; import org.hibernate.type.descriptor.jdbc.EnumJdbcType;
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType; import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimeAsTimestampWithTimeZoneJdbcType;
@ -294,7 +293,7 @@ public class H2LegacyDialect extends Dialect {
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
} }
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) { if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonJdbcType.INSTANCE );
} }
jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE );

View File

@ -66,8 +66,8 @@ dependencies {
testRuntimeOnly libs.byteBuddy testRuntimeOnly libs.byteBuddy
testRuntimeOnly testLibs.weld testRuntimeOnly testLibs.weld
testRuntimeOnly testLibs.wildFlyTxnClient testRuntimeOnly testLibs.wildFlyTxnClient
testRuntimeOnly jakartaLibs.jsonb testImplementation jakartaLibs.jsonb
testRuntimeOnly libs.jackson testImplementation libs.jackson
testRuntimeOnly libs.jacksonXml testRuntimeOnly libs.jacksonXml
testRuntimeOnly libs.jacksonJsr310 testRuntimeOnly libs.jacksonJsr310

View File

@ -65,7 +65,6 @@ import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.EnumJdbcType; import org.hibernate.type.descriptor.jdbc.EnumJdbcType;
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType; import org.hibernate.type.descriptor.jdbc.OrdinalEnumJdbcType;
import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType; import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType;
@ -244,7 +243,7 @@ public class H2Dialect extends Dialect {
jdbcTypeRegistry.addDescriptor( TimestampUtcAsInstantJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( TimestampUtcAsInstantJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE );
} }

View File

@ -0,0 +1,67 @@
/*
* 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.dialect;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
/**
* H2 requires binding JSON via {@code setBytes} methods.
*/
public class H2JsonJdbcType extends JsonJdbcType {
/**
* Singleton access
*/
public static final H2JsonJdbcType INSTANCE = new H2JsonJdbcType( null );
protected H2JsonJdbcType(EmbeddableMappingType embeddableMappingType) {
super( embeddableMappingType );
}
@Override
public String toString() {
return "FormatJsonJdbcType";
}
@Override
public AggregateJdbcType resolveAggregateJdbcType(
EmbeddableMappingType mappingType,
String sqlType,
RuntimeModelCreationContext creationContext) {
return new H2JsonJdbcType( mappingType );
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
final String json = ( (H2JsonJdbcType) getJdbcType() ).toString( value, getJavaType(), options );
st.setBytes( index, json.getBytes( StandardCharsets.UTF_8 ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String json = ( (H2JsonJdbcType) getJdbcType() ).toString( value, getJavaType(), options );
st.setBytes( name, json.getBytes( StandardCharsets.UTF_8 ) );
}
};
}
}

View File

@ -13,6 +13,8 @@ import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents a literal value in the sqm, e.g.<ul> * Represents a literal value in the sqm, e.g.<ul>
* <li>1</li> * <li>1</li>
@ -75,7 +77,11 @@ public class SqmLiteral<T> extends AbstractSqmExpression<T> {
appendHqlString( sb, getJavaTypeDescriptor(), getLiteralValue() ); appendHqlString( sb, getJavaTypeDescriptor(), getLiteralValue() );
} }
public static <T> void appendHqlString(StringBuilder sb, JavaType<T> javaType, T value) { public static <T> void appendHqlString(StringBuilder sb, JavaType<T> javaType, @Nullable T value) {
if ( value == null ) {
sb.append( "null" );
}
else {
final String string = javaType.toString( value ); final String string = javaType.toString( value );
if ( javaType.getJavaTypeClass() == String.class ) { if ( javaType.getJavaTypeClass() == String.class ) {
QueryLiteralHelper.appendStringLiteral( sb, string ); QueryLiteralHelper.appendStringLiteral( sb, string );
@ -84,5 +90,6 @@ public class SqmLiteral<T> extends AbstractSqmExpression<T> {
sb.append( string ); sb.append( string );
} }
} }
}
} }

View File

@ -296,7 +296,7 @@ public class EnumType<T extends Enum<T>>
@Override @SuppressWarnings("unchecked") @Override @SuppressWarnings("unchecked")
public String toLoggableString(Object value, SessionFactoryImplementor factory) { public String toLoggableString(Object value, SessionFactoryImplementor factory) {
verifyConfigured(); verifyConfigured();
return enumJavaType.toString( (T) value ); return enumJavaType.extractLoggableRepresentation( (T) value );
} }
public boolean isOrdinal() { public boolean isOrdinal() {

View File

@ -154,7 +154,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException A problem occurred performing the comparison * @throws HibernateException A problem occurred performing the comparison
*/ */
boolean isSame(Object x, Object y) throws HibernateException; boolean isSame(@Nullable Object x, @Nullable Object y) throws HibernateException;
/** /**
* Compare two instances of the class mapped by this type for persistence "equality", * Compare two instances of the class mapped by this type for persistence "equality",
@ -173,7 +173,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException A problem occurred performing the comparison * @throws HibernateException A problem occurred performing the comparison
*/ */
boolean isEqual(Object x, Object y) throws HibernateException; boolean isEqual(@Nullable Object x, @Nullable Object y) throws HibernateException;
/** /**
* Compare two instances of the class mapped by this type for persistence "equality", * Compare two instances of the class mapped by this type for persistence "equality",
@ -193,7 +193,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException A problem occurred performing the comparison * @throws HibernateException A problem occurred performing the comparison
*/ */
boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException; boolean isEqual(@Nullable Object x, @Nullable Object y, SessionFactoryImplementor factory) throws HibernateException;
/** /**
* Get a hash code, consistent with persistence "equality". For most types this could * Get a hash code, consistent with persistence "equality". For most types this could
@ -229,9 +229,9 @@ public interface Type extends Serializable {
* *
* @see java.util.Comparator#compare(Object, Object) * @see java.util.Comparator#compare(Object, Object)
*/ */
int compare(Object x, Object y); int compare(@Nullable Object x, @Nullable Object y);
int compare(Object x, Object y, SessionFactoryImplementor sessionFactory); int compare(@Nullable Object x, @Nullable Object y, SessionFactoryImplementor sessionFactory);
/** /**
* Should the parent be considered dirty, given both the old and current value? * Should the parent be considered dirty, given both the old and current value?
@ -244,7 +244,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException A problem occurred performing the checking * @throws HibernateException A problem occurred performing the checking
*/ */
boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) throws HibernateException; boolean isDirty(@Nullable Object old, @Nullable Object current, SharedSessionContractImplementor session) throws HibernateException;
/** /**
* Should the parent be considered dirty, given both the old and current value? * Should the parent be considered dirty, given both the old and current value?
@ -258,7 +258,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException A problem occurred performing the checking * @throws HibernateException A problem occurred performing the checking
*/ */
boolean isDirty(Object oldState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) boolean isDirty(@Nullable Object oldState, @Nullable Object currentState, boolean[] checkable, SharedSessionContractImplementor session)
throws HibernateException; throws HibernateException;
/** /**
@ -277,8 +277,8 @@ public interface Type extends Serializable {
* @throws HibernateException A problem occurred performing the checking * @throws HibernateException A problem occurred performing the checking
*/ */
boolean isModified( boolean isModified(
Object dbState, @Nullable Object dbState,
Object currentState, @Nullable Object currentState,
boolean[] checkable, boolean[] checkable,
SharedSessionContractImplementor session) SharedSessionContractImplementor session)
throws HibernateException; throws HibernateException;
@ -300,7 +300,7 @@ public interface Type extends Serializable {
*/ */
void nullSafeSet( void nullSafeSet(
PreparedStatement st, PreparedStatement st,
Object value, @Nullable Object value,
int index, int index,
boolean[] settable, boolean[] settable,
SharedSessionContractImplementor session) SharedSessionContractImplementor session)
@ -320,7 +320,7 @@ public interface Type extends Serializable {
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
* @throws SQLException An error from the JDBC driver * @throws SQLException An error from the JDBC driver
*/ */
void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) void nullSafeSet(PreparedStatement st, @Nullable Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException; throws HibernateException, SQLException;
/** /**
@ -353,7 +353,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
Object deepCopy(Object value, SessionFactoryImplementor factory) @Nullable Object deepCopy(@Nullable Object value, SessionFactoryImplementor factory)
throws HibernateException; throws HibernateException;
/** /**
@ -392,7 +392,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
default Serializable disassemble(Object value, SessionFactoryImplementor sessionFactory) throws HibernateException { default @Nullable Serializable disassemble(@Nullable Object value, SessionFactoryImplementor sessionFactory) throws HibernateException {
return disassemble( value, null, null ); return disassemble( value, null, null );
} }
@ -410,7 +410,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException; @Nullable Serializable disassemble(@Nullable Object value, @Nullable SharedSessionContractImplementor session, @Nullable Object owner) throws HibernateException;
/** /**
* Reconstruct the object from its disassembled state. This function is the inverse of * Reconstruct the object from its disassembled state. This function is the inverse of
@ -424,7 +424,7 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException; @Nullable Object assemble(@Nullable Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException;
/** /**
* Called before assembling a query result set from the query cache, to allow batch * Called before assembling a query result set from the query cache, to allow batch
@ -432,7 +432,9 @@ public interface Type extends Serializable {
* *
* @param cached The key * @param cached The key
* @param session The originating session * @param session The originating session
* @deprecated Is not called anymore
*/ */
@Deprecated(forRemoval = true, since = "6.6")
void beforeAssemble(Serializable cached, SharedSessionContractImplementor session); void beforeAssemble(Serializable cached, SharedSessionContractImplementor session);
/** /**
@ -452,9 +454,9 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
Object replace( @Nullable Object replace(
Object original, @Nullable Object original,
Object target, @Nullable Object target,
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
Object owner, Object owner,
Map<Object, Object> copyCache) throws HibernateException; Map<Object, Object> copyCache) throws HibernateException;
@ -477,9 +479,9 @@ public interface Type extends Serializable {
* *
* @throws HibernateException An error from Hibernate * @throws HibernateException An error from Hibernate
*/ */
Object replace( @Nullable Object replace(
Object original, @Nullable Object original,
Object target, @Nullable Object target,
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
Object owner, Object owner,
Map<Object, Object> copyCache, Map<Object, Object> copyCache,
@ -494,5 +496,5 @@ public interface Type extends Serializable {
* *
* @return array indicating column nullness for a value instance * @return array indicating column nullness for a value instance
*/ */
boolean[] toColumnNullness(Object value, Mapping mapping); boolean[] toColumnNullness(@Nullable Object value, Mapping mapping);
} }

View File

@ -26,6 +26,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Descriptor for the Java side of a value mapping. A {@code JavaType} is always * Descriptor for the Java side of a value mapping. A {@code JavaType} is always
* coupled with a {@link JdbcType} to describe the typing aspects of an attribute * coupled with a {@link JdbcType} to describe the typing aspects of an attribute
@ -228,12 +230,12 @@ public interface JavaType<T> extends Serializable {
* *
* @return The loggable representation * @return The loggable representation
*/ */
default String extractLoggableRepresentation(T value) { default String extractLoggableRepresentation(@Nullable T value) {
return toString( value ); return value == null ? "null" : toString( value );
} }
default String toString(T value) { default String toString(T value) {
return value == null ? "null" : value.toString(); return value.toString();
} }
T fromString(CharSequence string); T fromString(CharSequence string);

View File

@ -10,6 +10,8 @@ import java.io.Serializable;
import org.hibernate.SharedSessionContract; import org.hibernate.SharedSessionContract;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Describes the mutability aspects of a given Java type. * Describes the mutability aspects of a given Java type.
* <p> * <p>
@ -65,7 +67,7 @@ public interface MutabilityPlan<T> extends Serializable {
* *
* @return The deep copy. * @return The deep copy.
*/ */
T deepCopy(T value); @Nullable T deepCopy(@Nullable T value);
/** /**
* Return a disassembled representation of the value. * Return a disassembled representation of the value.
@ -76,7 +78,7 @@ public interface MutabilityPlan<T> extends Serializable {
* *
* @see #assemble * @see #assemble
*/ */
Serializable disassemble(T value, SharedSessionContract session); @Nullable Serializable disassemble(@Nullable T value, SharedSessionContract session);
/** /**
* Assemble a previously {@linkplain #disassemble disassembled} value. * Assemble a previously {@linkplain #disassemble disassembled} value.
@ -87,5 +89,5 @@ public interface MutabilityPlan<T> extends Serializable {
* *
* @see #disassemble * @see #disassemble
*/ */
T assemble(Serializable cached, SharedSessionContract session); @Nullable T assemble(@Nullable Serializable cached, SharedSessionContract session);
} }

View File

@ -30,15 +30,25 @@ public class ObjectArrayJavaType extends AbstractClassJavaType<Object[]> {
public String toString(Object[] value) { public String toString(Object[] value) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append( '(' ); sb.append( '(' );
sb.append( components[0].toString( value[0] ) ); append( sb, components, value, 0 );
for ( int i = 1; i < components.length; i++ ) { for ( int i = 1; i < components.length; i++ ) {
sb.append( ", " ); sb.append( ", " );
sb.append( components[i].toString( value[i] ) ); append( sb, components, value, i );
} }
sb.append( ')' ); sb.append( ')' );
return sb.toString(); return sb.toString();
} }
private void append(StringBuilder sb, JavaType[] components, Object[] value, int i) {
final Object o = value[i];
if (o == null ) {
sb.append( "null" );
}
else {
sb.append( components[i].toString( o ) );
}
}
@Override @Override
public boolean areEqual(Object[] one, Object[] another) { public boolean areEqual(Object[] one, Object[] another) {
if ( one == another ) { if ( one == another ) {

View File

@ -160,7 +160,7 @@ public class BasicCollectionJavaType<C extends Collection<E>, E> extends Abstrac
sb.append( '[' ); sb.append( '[' );
do { do {
final E element = iterator.next(); final E element = iterator.next();
sb.append( componentJavaType.toString( element ) ); sb.append( componentJavaType.extractLoggableRepresentation( element ) );
if ( !iterator.hasNext() ) { if ( !iterator.hasNext() ) {
return sb.append( ']' ).toString(); return sb.append( ']' ).toString();
} }

View File

@ -24,6 +24,7 @@ import org.hibernate.type.spi.TypeConfiguration;
* or {@link org.hibernate.type.SqlTypes#SQLXML} mapped types. * or {@link org.hibernate.type.SqlTypes#SQLXML} mapped types.
* *
* @author Christian Beikov * @author Christian Beikov
* @author Yanming Zhou
*/ */
@Incubating @Incubating
public abstract class FormatMapperBasedJavaType<T> extends AbstractJavaType<T> implements MutabilityPlan<T> { public abstract class FormatMapperBasedJavaType<T> extends AbstractJavaType<T> implements MutabilityPlan<T> {
@ -107,16 +108,16 @@ public abstract class FormatMapperBasedJavaType<T> extends AbstractJavaType<T> i
@Override @Override
public T deepCopy(T value) { public T deepCopy(T value) {
return fromString( toString( value ) ); return value == null ? null : fromString( toString( value ) );
} }
@Override @Override
public Serializable disassemble(T value, SharedSessionContract session) { public Serializable disassemble(T value, SharedSessionContract session) {
return toString( value ); return value == null ? null : toString( value );
} }
@Override @Override
public T assemble(Serializable cached, SharedSessionContract session) { public T assemble(Serializable cached, SharedSessionContract session) {
return fromString( (CharSequence) cached ); return cached == null ? null : fromString( (CharSequence) cached );
} }
} }

View File

@ -16,7 +16,9 @@ import org.hibernate.sql.ast.spi.SqlAppender;
* '{@code ? format json}' write expression for H2. * '{@code ? format json}' write expression for H2.
* *
* @author Marco Belladelli * @author Marco Belladelli
* @deprecated Use {@link org.hibernate.dialect.H2JsonJdbcType} instead
*/ */
@Deprecated(forRemoval = true, since = "6.6")
public class H2FormatJsonJdbcType extends JsonJdbcType { public class H2FormatJsonJdbcType extends JsonJdbcType {
/** /**
* Singleton access * Singleton access

View File

@ -0,0 +1,41 @@
/*
* 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.format;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import java.lang.reflect.Type;
/**
* @author Yanming Zhou
*/
public abstract class AbstractJsonFormatMapper implements FormatMapper {
@SuppressWarnings("unchecked")
@Override
public final <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
final Type type = javaType.getJavaType();
if ( type == String.class || type == Object.class ) {
return (T) charSequence.toString();
}
return fromString( charSequence, type );
}
@Override
public final <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
final Type type = javaType.getJavaType();
if ( type == String.class || type == Object.class ) {
return (String) value;
}
return toString( value, type );
}
protected abstract <T> T fromString(CharSequence charSequence, Type type);
protected abstract <T> String toString(T value, Type type);
}

View File

@ -6,17 +6,18 @@
*/ */
package org.hibernate.type.format.jackson; package org.hibernate.type.format.jackson;
import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.AbstractJsonFormatMapper;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
/** /**
* @author Christian Beikov * @author Christian Beikov
* @author Yanming Zhou
*/ */
public final class JacksonJsonFormatMapper implements FormatMapper { public final class JacksonJsonFormatMapper extends AbstractJsonFormatMapper {
public static final String SHORT_NAME = "jackson"; public static final String SHORT_NAME = "jackson";
@ -31,29 +32,22 @@ public final class JacksonJsonFormatMapper implements FormatMapper {
} }
@Override @Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) { public <T> T fromString(CharSequence charSequence, Type type) {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (T) charSequence.toString();
}
try { try {
return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( javaType.getJavaType() ) ); return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( type ) );
} }
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e ); throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e );
} }
} }
@Override @Override
public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) { public <T> String toString(T value, Type type) {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (String) value;
}
try { try {
return objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ) return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value );
.writeValueAsString( value );
} }
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e ); throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e );
} }
} }
} }

View File

@ -6,18 +6,19 @@
*/ */
package org.hibernate.type.format.jakartajson; package org.hibernate.type.format.jakartajson;
import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.AbstractJsonFormatMapper;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.json.bind.Jsonb; import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbException; import jakarta.json.bind.JsonbException;
import java.lang.reflect.Type;
/** /**
* @author Christian Beikov * @author Christian Beikov
* @author Yanming Zhou
*/ */
public final class JsonBJsonFormatMapper implements FormatMapper { public final class JsonBJsonFormatMapper extends AbstractJsonFormatMapper {
public static final String SHORT_NAME = "jsonb"; public static final String SHORT_NAME = "jsonb";
@ -32,28 +33,22 @@ public final class JsonBJsonFormatMapper implements FormatMapper {
} }
@Override @Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) { public <T> T fromString(CharSequence charSequence, Type type) {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (T) charSequence.toString();
}
try { try {
return jsonb.fromJson( charSequence.toString(), javaType.getJavaType() ); return jsonb.fromJson( charSequence.toString(), type );
} }
catch (JsonbException e) { catch (JsonbException e) {
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e ); throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e );
} }
} }
@Override @Override
public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) { public <T> String toString(T value, Type type) {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (String) value;
}
try { try {
return jsonb.toJson( value, javaType.getJavaType() ); return jsonb.toJson( value, type );
} }
catch (JsonbException e) { catch (JsonbException e) {
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e ); throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e );
} }
} }
} }

View File

@ -12,16 +12,20 @@ import java.sql.Clob;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.json.JsonValue;
import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.AltibaseDialect;
import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.SybaseDialect;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
@ -47,9 +51,11 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.isA; import static org.hamcrest.Matchers.isA;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
/** /**
* @author Christian Beikov * @author Christian Beikov
* @author Yanming Zhou
*/ */
@DomainModel(annotatedClasses = JsonMappingTests.EntityWithJson.class) @DomainModel(annotatedClasses = JsonMappingTests.EntityWithJson.class)
@SessionFactory @SessionFactory
@ -140,6 +146,29 @@ public abstract class JsonMappingTests {
assertThat( entityWithJson.stringMap, is( stringMap ) ); assertThat( entityWithJson.stringMap, is( stringMap ) );
assertThat( entityWithJson.objectMap, is( objectMap ) ); assertThat( entityWithJson.objectMap, is( objectMap ) );
assertThat( entityWithJson.list, is( list ) ); assertThat( entityWithJson.list, is( list ) );
assertThat( entityWithJson.jsonNode, is( nullValue() ));
assertThat( entityWithJson.jsonValue, is( nullValue() ));
}
);
}
@Test
public void verifyMergeWorks(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.merge( new EntityWithJson( 2, null, null, null, null ) );
}
);
scope.inTransaction(
(session) -> {
EntityWithJson entityWithJson = session.find( EntityWithJson.class, 2 );
assertThat( entityWithJson.stringMap, is( nullValue() ) );
assertThat( entityWithJson.objectMap, is( nullValue() ) );
assertThat( entityWithJson.list, is( nullValue() ) );
assertThat( entityWithJson.jsonString, is( nullValue() ) );
assertThat( entityWithJson.jsonNode, is( nullValue() ));
assertThat( entityWithJson.jsonValue, is( nullValue() ));
} }
); );
} }
@ -234,6 +263,12 @@ public abstract class JsonMappingTests {
@JdbcTypeCode( SqlTypes.JSON ) @JdbcTypeCode( SqlTypes.JSON )
private String jsonString; private String jsonString;
@JdbcTypeCode( SqlTypes.JSON )
private JsonNode jsonNode;
@JdbcTypeCode( SqlTypes.JSON )
private JsonValue jsonValue;
public EntityWithJson() { public EntityWithJson() {
} }