Skip to content

Commit 47e8272

Browse files
The-Huginnbeikov
authored andcommitted
[HHH-17294] DeepCopy non-Embedded JSON or XML JdbcTypCode attribute using FormatMapper
1 parent 2e0f0c5 commit 47e8272

File tree

10 files changed

+366
-47
lines changed

10 files changed

+366
-47
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public enum Kind {
152152
private TemporalType temporalPrecision;
153153
private TimeZoneStorageType timeZoneStorageType;
154154
private boolean partitionKey;
155+
private Integer jdbcTypeCode;
155156

156157
private Table table;
157158
private AnnotatedColumns columns;
@@ -1072,6 +1073,12 @@ private void normalSupplementalDetails(XProperty attributeXProperty) {
10721073
return null;
10731074
};
10741075

1076+
final org.hibernate.annotations.JdbcTypeCode jdbcType =
1077+
findAnnotation( attributeXProperty, org.hibernate.annotations.JdbcTypeCode.class );
1078+
if ( jdbcType != null ) {
1079+
jdbcTypeCode = jdbcType.value();
1080+
}
1081+
10751082
normalJdbcTypeDetails( attributeXProperty);
10761083
normalMutabilityDetails( attributeXProperty );
10771084

@@ -1223,6 +1230,10 @@ public BasicValue make() {
12231230
basicValue.setTemporalPrecision( temporalPrecision );
12241231
}
12251232

1233+
if ( jdbcTypeCode != null ) {
1234+
basicValue.setExplicitJdbcTypeCode( jdbcTypeCode );
1235+
}
1236+
12261237
linkWithValue();
12271238

12281239
boolean isInSecondPass = buildingContext.getMetadataCollector().isInSecondPass();

hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.function.Consumer;
1313
import java.util.function.Function;
1414

15+
import org.hibernate.Incubating;
1516
import org.hibernate.Internal;
1617
import org.hibernate.MappingException;
1718
import org.hibernate.TimeZoneStorageStrategy;
@@ -46,13 +47,18 @@
4647
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
4748
import org.hibernate.type.BasicType;
4849
import org.hibernate.type.CustomType;
50+
import org.hibernate.type.SqlTypes;
4951
import org.hibernate.type.Type;
5052
import org.hibernate.type.WrapperArrayHandling;
5153
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
5254
import org.hibernate.type.descriptor.java.BasicJavaType;
5355
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
5456
import org.hibernate.type.descriptor.java.JavaType;
5557
import org.hibernate.type.descriptor.java.MutabilityPlan;
58+
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
59+
import org.hibernate.type.descriptor.java.spi.JsonJavaType;
60+
import org.hibernate.type.descriptor.java.spi.RegistryHelper;
61+
import org.hibernate.type.descriptor.java.spi.XmlJavaType;
5662
import org.hibernate.type.descriptor.jdbc.JdbcType;
5763
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
5864
import org.hibernate.type.internal.BasicTypeImpl;
@@ -97,6 +103,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
97103
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
98104
// Resolved state - available after `#resolve`
99105
private Resolution<?> resolution;
106+
private Integer jdbcTypeCode;
100107

101108

102109
public BasicValue(MetadataBuildingContext buildingContext) {
@@ -495,15 +502,15 @@ public TypeConfiguration getTypeConfiguration() {
495502

496503
private JavaType<?> determineJavaType(JavaType<?> explicitJavaType) {
497504
JavaType<?> javaType = explicitJavaType;
498-
499-
if ( javaType == null ) {
500-
if ( implicitJavaTypeAccess != null ) {
501-
final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() );
502-
if ( implicitJtd != null ) {
503-
javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd );
504-
}
505-
}
506-
}
505+
//
506+
// if ( javaType == null ) {
507+
// if ( implicitJavaTypeAccess != null ) {
508+
// final java.lang.reflect.Type implicitJtd = implicitJavaTypeAccess.apply( getTypeConfiguration() );
509+
// if ( implicitJtd != null ) {
510+
// javaType = getTypeConfiguration().getJavaTypeRegistry().getDescriptor( implicitJtd );
511+
// }
512+
// }
513+
// }
507514

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

528+
final TypeConfiguration typeConfiguration = getTypeConfiguration();
521529
if ( resolvedJavaType != null ) {
522530
impliedJavaType = resolvedJavaType;
523531
}
524532
else if ( implicitJavaTypeAccess != null ) {
525-
impliedJavaType = implicitJavaTypeAccess.apply( getTypeConfiguration() );
533+
impliedJavaType = implicitJavaTypeAccess.apply( typeConfiguration );
526534
}
527535
else if ( ownerName != null && propertyName != null ) {
528536
impliedJavaType = ReflectHelper.reflectedPropertyType(
@@ -541,7 +549,40 @@ else if ( ownerName != null && propertyName != null ) {
541549
return null;
542550
}
543551

544-
return getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( impliedJavaType );
552+
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
553+
final JavaType<Object> javaType = javaTypeRegistry.findDescriptor( impliedJavaType );
554+
final MutabilityPlan<Object> explicitMutabilityPlan = explicitMutabilityPlanAccess != null
555+
? explicitMutabilityPlanAccess.apply( typeConfiguration )
556+
: null;
557+
final MutabilityPlan<Object> determinedMutabilityPlan = explicitMutabilityPlan != null
558+
? explicitMutabilityPlan
559+
: RegistryHelper.INSTANCE.determineMutabilityPlan( impliedJavaType, typeConfiguration );
560+
if ( javaType == null ) {
561+
if ( jdbcTypeCode != null ) {
562+
// Construct special JavaType instances for JSON/XML types which can report recommended JDBC types
563+
// and implement toString/fromString as well as copying based on FormatMapper operations
564+
switch ( jdbcTypeCode ) {
565+
case SqlTypes.JSON:
566+
final JavaType<Object> jsonJavaType = new JsonJavaType<>(
567+
impliedJavaType,
568+
determinedMutabilityPlan,
569+
typeConfiguration
570+
);
571+
javaTypeRegistry.addDescriptor( jsonJavaType );
572+
return jsonJavaType;
573+
case SqlTypes.SQLXML:
574+
final JavaType<Object> xmlJavaType = new XmlJavaType<>(
575+
impliedJavaType,
576+
determinedMutabilityPlan,
577+
typeConfiguration
578+
);
579+
javaTypeRegistry.addDescriptor( xmlJavaType );
580+
return xmlJavaType;
581+
}
582+
}
583+
return javaTypeRegistry.resolveDescriptor( impliedJavaType );
584+
}
585+
return javaType;
545586
}
546587

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

915+
@Incubating
916+
public void setExplicitJdbcTypeCode(Integer jdbcTypeCode) {
917+
this.jdbcTypeCode = jdbcTypeCode;
918+
}
919+
874920
/**
875921
* Resolved form of {@link BasicValue} as part of interpreting the
876922
* boot-time model into the run-time model

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
3131
import org.hibernate.type.descriptor.java.JavaType;
3232
import org.hibernate.type.descriptor.java.MutabilityPlan;
33+
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
3334
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
3435
import org.hibernate.type.descriptor.jdbc.JdbcType;
3536

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

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

248251
@Override
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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.type.descriptor.java.spi;
8+
9+
import java.io.Serializable;
10+
import java.lang.reflect.Type;
11+
12+
import org.hibernate.Incubating;
13+
import org.hibernate.SharedSessionContract;
14+
import org.hibernate.type.descriptor.WrapperOptions;
15+
import org.hibernate.type.descriptor.java.AbstractJavaType;
16+
import org.hibernate.type.descriptor.java.MutabilityPlan;
17+
import org.hibernate.type.descriptor.jdbc.JdbcType;
18+
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
19+
import org.hibernate.type.format.FormatMapper;
20+
import org.hibernate.type.spi.TypeConfiguration;
21+
22+
/**
23+
* Java type for {@link FormatMapper} based types i.e. {@link org.hibernate.type.SqlTypes#JSON}
24+
* or {@link org.hibernate.type.SqlTypes#SQLXML} mapped types.
25+
*
26+
* @author Christian Beikov
27+
*/
28+
@Incubating
29+
public abstract class FormatMapperBasedJavaType<T> extends AbstractJavaType<T> implements MutabilityPlan<T> {
30+
31+
private final TypeConfiguration typeConfiguration;
32+
33+
public FormatMapperBasedJavaType(
34+
Type type,
35+
MutabilityPlan<T> mutabilityPlan,
36+
TypeConfiguration typeConfiguration) {
37+
super( type, mutabilityPlan );
38+
this.typeConfiguration = typeConfiguration;
39+
}
40+
41+
protected abstract FormatMapper getFormatMapper(TypeConfiguration typeConfiguration);
42+
43+
@Override
44+
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
45+
throw new JdbcTypeRecommendationException(
46+
"Could not determine recommended JdbcType for Java type '" + getJavaType().getTypeName() + "'"
47+
);
48+
}
49+
50+
@Override
51+
public String toString(T value) {
52+
return getFormatMapper( typeConfiguration ).toString(
53+
value,
54+
this,
55+
typeConfiguration.getSessionFactory().getWrapperOptions()
56+
);
57+
}
58+
59+
@Override
60+
public T fromString(CharSequence string) {
61+
return getFormatMapper( typeConfiguration ).fromString(
62+
string,
63+
this,
64+
typeConfiguration.getSessionFactory().getWrapperOptions()
65+
);
66+
}
67+
68+
@Override
69+
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
70+
if ( type.isAssignableFrom( getJavaTypeClass() ) ) {
71+
//noinspection unchecked
72+
return (X) value;
73+
}
74+
else if ( type == String.class ) {
75+
//noinspection unchecked
76+
return (X) getFormatMapper( typeConfiguration ).toString( value, this, options );
77+
}
78+
throw new UnsupportedOperationException(
79+
"Unwrap strategy not known for this Java type : " + getJavaType().getTypeName()
80+
);
81+
}
82+
83+
@Override
84+
public <X> T wrap(X value, WrapperOptions options) {
85+
if ( getJavaTypeClass().isInstance( value ) ) {
86+
//noinspection unchecked
87+
return (T) value;
88+
}
89+
else if ( value instanceof String ) {
90+
return getFormatMapper( typeConfiguration ).fromString( (String) value, this, options );
91+
}
92+
throw new UnsupportedOperationException(
93+
"Wrap strategy not known for this Java type : " + getJavaType().getTypeName()
94+
);
95+
}
96+
97+
@Override
98+
public MutabilityPlan<T> getMutabilityPlan() {
99+
final MutabilityPlan<T> mutabilityPlan = super.getMutabilityPlan();
100+
return mutabilityPlan == null ? this : mutabilityPlan;
101+
}
102+
103+
@Override
104+
public boolean isMutable() {
105+
return true;
106+
}
107+
108+
@Override
109+
public T deepCopy(T value) {
110+
return fromString( toString( value ) );
111+
}
112+
113+
@Override
114+
public Serializable disassemble(T value, SharedSessionContract session) {
115+
return toString( value );
116+
}
117+
118+
@Override
119+
public T assemble(Serializable cached, SharedSessionContract session) {
120+
return fromString( (CharSequence) cached );
121+
}
122+
}

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.lang.reflect.ParameterizedType;
1111
import java.lang.reflect.Type;
1212
import java.util.concurrent.ConcurrentHashMap;
13+
import java.util.function.BiFunction;
1314
import java.util.function.Consumer;
1415
import java.util.function.Supplier;
1516

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

112113
public <J> JavaType<J> resolveDescriptor(Type javaType) {
114+
return resolveDescriptor( javaType, (elementJavaType, typeConfiguration) -> {
115+
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan(
116+
elementJavaType,
117+
typeConfiguration
118+
);
119+
if ( determinedPlan != null ) {
120+
return determinedPlan;
121+
}
122+
123+
return MutableMutabilityPlan.INSTANCE;
124+
} );
125+
}
126+
127+
public <J> JavaType<J> resolveDescriptor(
128+
Type javaType,
129+
BiFunction<Type, TypeConfiguration, MutabilityPlan<?>> mutabilityPlanCreator) {
113130
return resolveDescriptor(
114131
javaType,
115132
() -> {
@@ -131,21 +148,10 @@ public <J> JavaType<J> resolveDescriptor(Type javaType) {
131148
elementTypeDescriptor = null;
132149
}
133150
if ( elementTypeDescriptor == null ) {
151+
//noinspection unchecked
134152
elementTypeDescriptor = RegistryHelper.INSTANCE.createTypeDescriptor(
135153
elementJavaType,
136-
() -> {
137-
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan(
138-
elementJavaType,
139-
typeConfiguration
140-
);
141-
if ( determinedPlan != null ) {
142-
return determinedPlan;
143-
}
144-
145-
//noinspection unchecked
146-
return (MutabilityPlan<J>) MutableMutabilityPlan.INSTANCE;
147-
148-
},
154+
() -> (MutabilityPlan<J>) mutabilityPlanCreator.apply( elementJavaType, typeConfiguration ),
149155
typeConfiguration
150156
);
151157
}

0 commit comments

Comments
 (0)