HHH-17840 Fix inconsistency of read/write null JsonNode/JsonValue
This commit is contained in:
parent
a882fbdf0c
commit
c5d5bc1922
|
@ -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 );
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,13 +77,18 @@ 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) {
|
||||||
final String string = javaType.toString( value );
|
if ( value == null ) {
|
||||||
if ( javaType.getJavaTypeClass() == String.class ) {
|
sb.append( "null" );
|
||||||
QueryLiteralHelper.appendStringLiteral( sb, string );
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sb.append( string );
|
final String string = javaType.toString( value );
|
||||||
|
if ( javaType.getJavaTypeClass() == String.class ) {
|
||||||
|
QueryLiteralHelper.appendStringLiteral( sb, string );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append( string );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue