Skip to content

Commit

Permalink
[HHH-17294] DeepCopy non-Embedded JSON or XML JdbcTypCode attribute u…
Browse files Browse the repository at this point in the history
…sing FormatMapper
  • Loading branch information
The-Huginn authored and beikov committed Dec 1, 2023
1 parent 2e0f0c5 commit 47e8272
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ public enum Kind {
private TemporalType temporalPrecision;
private TimeZoneStorageType timeZoneStorageType;
private boolean partitionKey;
private Integer jdbcTypeCode;

private Table table;
private AnnotatedColumns columns;
Expand Down Expand Up @@ -1072,6 +1073,12 @@ private void normalSupplementalDetails(XProperty attributeXProperty) {
return null;
};

final org.hibernate.annotations.JdbcTypeCode jdbcType =
findAnnotation( attributeXProperty, org.hibernate.annotations.JdbcTypeCode.class );
if ( jdbcType != null ) {
jdbcTypeCode = jdbcType.value();
}

normalJdbcTypeDetails( attributeXProperty);
normalMutabilityDetails( attributeXProperty );

Expand Down Expand Up @@ -1223,6 +1230,10 @@ public BasicValue make() {
basicValue.setTemporalPrecision( temporalPrecision );
}

if ( jdbcTypeCode != null ) {
basicValue.setExplicitJdbcTypeCode( jdbcTypeCode );
}

linkWithValue();

boolean isInSecondPass = buildingContext.getMetadataCollector().isInSecondPass();
Expand Down
68 changes: 57 additions & 11 deletions hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.function.Consumer;
import java.util.function.Function;

import org.hibernate.Incubating;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.TimeZoneStorageStrategy;
Expand Down Expand Up @@ -46,13 +47,18 @@
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.CustomType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.Type;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.java.spi.JsonJavaType;
import org.hibernate.type.descriptor.java.spi.RegistryHelper;
import org.hibernate.type.descriptor.java.spi.XmlJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.internal.BasicTypeImpl;
Expand Down Expand Up @@ -97,6 +103,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Resolved state - available after `#resolve`
private Resolution<?> resolution;
private Integer jdbcTypeCode;


public BasicValue(MetadataBuildingContext buildingContext) {
Expand Down Expand Up @@ -495,15 +502,15 @@ public TypeConfiguration getTypeConfiguration() {

private JavaType<?> determineJavaType(JavaType<?> explicitJavaType) {
JavaType<?> javaType = explicitJavaType;

if ( javaType == null ) {
if ( implicitJavaTypeAccess != null ) {
final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() );
if ( implicitJtd != null ) {
javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd );
}
}
}
//
// if ( javaType == null ) {
// if ( implicitJavaTypeAccess != null ) {
// final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() );
// if ( implicitJtd != null ) {
// javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd );
// }
// }
// }

if ( javaType == null ) {
final JavaType<?> reflectedJtd = determineReflectedJavaType();
Expand All @@ -518,11 +525,12 @@ private JavaType<?> determineJavaType(JavaType<?> explicitJavaType) {
private JavaType<?> determineReflectedJavaType() {
final java.lang.reflect.Type impliedJavaType;

final TypeConfiguration typeConfiguration = getTypeConfiguration();
if ( resolvedJavaType != null ) {
impliedJavaType = resolvedJavaType;
}
else if ( implicitJavaTypeAccess != null ) {
impliedJavaType = implicitJavaTypeAccess.apply( getTypeConfiguration() );
impliedJavaType = implicitJavaTypeAccess.apply( typeConfiguration );
}
else if ( ownerName != null && propertyName != null ) {
impliedJavaType = ReflectHelper.reflectedPropertyType(
Expand All @@ -541,7 +549,40 @@ else if ( ownerName != null && propertyName != null ) {
return null;
}

return getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( impliedJavaType );
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
final JavaType<Object> javaType = javaTypeRegistry.findDescriptor( impliedJavaType );
final MutabilityPlan<Object> explicitMutabilityPlan = explicitMutabilityPlanAccess != null
? explicitMutabilityPlanAccess.apply( typeConfiguration )
: null;
final MutabilityPlan<Object> determinedMutabilityPlan = explicitMutabilityPlan != null
? explicitMutabilityPlan
: RegistryHelper.INSTANCE.determineMutabilityPlan( impliedJavaType, typeConfiguration );
if ( javaType == null ) {
if ( jdbcTypeCode != null ) {
// Construct special JavaType instances for JSON/XML types which can report recommended JDBC types
// and implement toString/fromString as well as copying based on FormatMapper operations
switch ( jdbcTypeCode ) {
case SqlTypes.JSON:
final JavaType<Object> jsonJavaType = new JsonJavaType<>(
impliedJavaType,
determinedMutabilityPlan,
typeConfiguration
);
javaTypeRegistry.addDescriptor( jsonJavaType );
return jsonJavaType;
case SqlTypes.SQLXML:
final JavaType<Object> xmlJavaType = new XmlJavaType<>(
impliedJavaType,
determinedMutabilityPlan,
typeConfiguration
);
javaTypeRegistry.addDescriptor( xmlJavaType );
return xmlJavaType;
}
}
return javaTypeRegistry.resolveDescriptor( impliedJavaType );
}
return javaType;
}

private static Resolution<?> interpretExplicitlyNamedType(
Expand Down Expand Up @@ -871,6 +912,11 @@ private boolean isWrapperByteOrCharacterArray() {
return javaTypeClass == Byte[].class || javaTypeClass == Character[].class;
}

@Incubating
public void setExplicitJdbcTypeCode(Integer jdbcTypeCode) {
this.jdbcTypeCode = jdbcTypeCode;
}

/**
* Resolved form of {@link BasicValue} as part of interpreting the
* boot-time model into the run-time model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;

Expand Down Expand Up @@ -242,7 +243,9 @@ public final boolean isDirty(Object old, Object current, boolean[] checkable, Sh
}

protected final boolean isDirty(Object old, Object current) {
return !isSame( old, current );
// MutableMutabilityPlan.INSTANCE is a special plan for which we always have to assume the value is dirty,
// because we can't actually copy a value, but have no knowledge about the mutability of the java type
return getMutabilityPlan() == MutableMutabilityPlan.INSTANCE || !isSame( old, current );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.type.descriptor.java.spi;

import java.io.Serializable;
import java.lang.reflect.Type;

import org.hibernate.Incubating;
import org.hibernate.SharedSessionContract;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractJavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.format.FormatMapper;
import org.hibernate.type.spi.TypeConfiguration;

/**
* Java type for {@link FormatMapper} based types i.e. {@link org.hibernate.type.SqlTypes#JSON}
* or {@link org.hibernate.type.SqlTypes#SQLXML} mapped types.
*
* @author Christian Beikov
*/
@Incubating
public abstract class FormatMapperBasedJavaType<T> extends AbstractJavaType<T> implements MutabilityPlan<T> {

private final TypeConfiguration typeConfiguration;

public FormatMapperBasedJavaType(
Type type,
MutabilityPlan<T> mutabilityPlan,
TypeConfiguration typeConfiguration) {
super( type, mutabilityPlan );
this.typeConfiguration = typeConfiguration;
}

protected abstract FormatMapper getFormatMapper(TypeConfiguration typeConfiguration);

@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
throw new JdbcTypeRecommendationException(
"Could not determine recommended JdbcType for Java type '" + getJavaType().getTypeName() + "'"
);
}

@Override
public String toString(T value) {
return getFormatMapper( typeConfiguration ).toString(
value,
this,
typeConfiguration.getSessionFactory().getWrapperOptions()
);
}

@Override
public T fromString(CharSequence string) {
return getFormatMapper( typeConfiguration ).fromString(
string,
this,
typeConfiguration.getSessionFactory().getWrapperOptions()
);
}

@Override
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
if ( type.isAssignableFrom( getJavaTypeClass() ) ) {
//noinspection unchecked
return (X) value;
}
else if ( type == String.class ) {
//noinspection unchecked
return (X) getFormatMapper( typeConfiguration ).toString( value, this, options );
}
throw new UnsupportedOperationException(
"Unwrap strategy not known for this Java type : " + getJavaType().getTypeName()
);
}

@Override
public <X> T wrap(X value, WrapperOptions options) {
if ( getJavaTypeClass().isInstance( value ) ) {
//noinspection unchecked
return (T) value;
}
else if ( value instanceof String ) {
return getFormatMapper( typeConfiguration ).fromString( (String) value, this, options );
}
throw new UnsupportedOperationException(
"Wrap strategy not known for this Java type : " + getJavaType().getTypeName()
);
}

@Override
public MutabilityPlan<T> getMutabilityPlan() {
final MutabilityPlan<T> mutabilityPlan = super.getMutabilityPlan();
return mutabilityPlan == null ? this : mutabilityPlan;
}

@Override
public boolean isMutable() {
return true;
}

@Override
public T deepCopy(T value) {
return fromString( toString( value ) );
}

@Override
public Serializable disassemble(T value, SharedSessionContract session) {
return toString( value );
}

@Override
public T assemble(Serializable cached, SharedSessionContract session) {
return fromString( (CharSequence) cached );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;

Expand Down Expand Up @@ -110,6 +111,22 @@ public <J> JavaType<J> resolveDescriptor(Type javaType, Supplier<JavaType<J>> cr
}

public <J> JavaType<J> resolveDescriptor(Type javaType) {
return resolveDescriptor( javaType, (elementJavaType, typeConfiguration) -> {
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan(
elementJavaType,
typeConfiguration
);
if ( determinedPlan != null ) {
return determinedPlan;
}

return MutableMutabilityPlan.INSTANCE;
} );
}

public <J> JavaType<J> resolveDescriptor(
Type javaType,
BiFunction<Type, TypeConfiguration, MutabilityPlan<?>> mutabilityPlanCreator) {
return resolveDescriptor(
javaType,
() -> {
Expand All @@ -131,21 +148,10 @@ public <J> JavaType<J> resolveDescriptor(Type javaType) {
elementTypeDescriptor = null;
}
if ( elementTypeDescriptor == null ) {
//noinspection unchecked
elementTypeDescriptor = RegistryHelper.INSTANCE.createTypeDescriptor(
elementJavaType,
() -> {
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan(
elementJavaType,
typeConfiguration
);
if ( determinedPlan != null ) {
return determinedPlan;
}

//noinspection unchecked
return (MutabilityPlan<J>) MutableMutabilityPlan.INSTANCE;

},
() -> (MutabilityPlan<J>) mutabilityPlanCreator.apply( elementJavaType, typeConfiguration ),
typeConfiguration
);
}
Expand Down

0 comments on commit 47e8272

Please sign in to comment.