Skip to content

Commit 9838d36

Browse files
committed
HHH-17680 Avoid JavaType collisions with special JavaTypes for JSON/XML
1 parent 6cfdc64 commit 9838d36

File tree

3 files changed

+169
-12
lines changed

3 files changed

+169
-12
lines changed

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -764,19 +764,25 @@ private JavaType<?> specialJavaType(
764764
// and implement toString/fromString as well as copying based on FormatMapper operations
765765
switch ( jdbcTypeCode ) {
766766
case SqlTypes.JSON:
767-
final var jsonJavaType =
768-
new JsonJavaType<>( impliedJavaType,
767+
return javaTypeRegistry.resolveDescriptor(
768+
SqlTypes.JSON,
769+
impliedJavaType,
770+
() -> new JsonJavaType<>(
771+
impliedJavaType,
769772
mutabilityPlan( typeConfiguration, impliedJavaType ),
770-
typeConfiguration );
771-
javaTypeRegistry.addDescriptor( jsonJavaType );
772-
return jsonJavaType;
773+
typeConfiguration
774+
)
775+
);
773776
case SqlTypes.SQLXML:
774-
final var xmlJavaType =
775-
new XmlJavaType<>( impliedJavaType,
777+
return javaTypeRegistry.resolveDescriptor(
778+
SqlTypes.SQLXML,
779+
impliedJavaType,
780+
() -> new XmlJavaType<>(
781+
impliedJavaType,
776782
mutabilityPlan( typeConfiguration, impliedJavaType ),
777-
typeConfiguration );
778-
javaTypeRegistry.addDescriptor( xmlJavaType );
779-
return xmlJavaType;
783+
typeConfiguration
784+
)
785+
);
780786
}
781787
}
782788
return javaTypeRegistry.getDescriptor( impliedJavaType );

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

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

15+
import org.checkerframework.checker.nullness.qual.Nullable;
1516
import org.hibernate.type.descriptor.java.ArrayJavaType;
1617
import org.hibernate.type.descriptor.java.JavaType;
1718
import org.hibernate.type.descriptor.java.MutabilityPlan;
@@ -35,6 +36,7 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial
3536

3637
private final TypeConfiguration typeConfiguration;
3738
private final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName = new ConcurrentHashMap<>();
39+
private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, JavaType<?>>> typeCodeSpecificDescriptorsByTypeName = new ConcurrentHashMap<>();
3840

3941
public JavaTypeRegistry(TypeConfiguration typeConfiguration) {
4042
this.typeConfiguration = typeConfiguration;
@@ -72,9 +74,22 @@ private void performInjections(JavaType<?> descriptor) {
7274

7375
public void forEachDescriptor(Consumer<JavaType<?>> consumer) {
7476
descriptorsByTypeName.values().forEach( consumer );
77+
typeCodeSpecificDescriptorsByTypeName.values().forEach( descriptorsByTypeName -> {
78+
descriptorsByTypeName.values().forEach( consumer );
79+
} );
7580
}
7681

7782
public void addDescriptor(JavaType<?> descriptor) {
83+
addDescriptor( descriptorsByTypeName, descriptor );
84+
}
85+
86+
public void addDescriptor(int sqlTypeCode, JavaType<?> descriptor) {
87+
final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName =
88+
typeCodeSpecificDescriptorsByTypeName.computeIfAbsent( sqlTypeCode, k -> new ConcurrentHashMap<>() );
89+
addDescriptor( descriptorsByTypeName, descriptor );
90+
}
91+
92+
private void addDescriptor(ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName, JavaType<?> descriptor) {
7893
final JavaType<?> old = descriptorsByTypeName.put( descriptor.getJavaType().getTypeName(), descriptor );
7994
if ( old != null ) {
8095
LOG.debugf(
@@ -93,22 +108,50 @@ public <T> JavaType<T> getDescriptor(Type javaType) {
93108
return (JavaType<T>) resolveDescriptor( javaType );
94109
}
95110

96-
public JavaType<?> findDescriptor(Type javaType) {
111+
public @Nullable JavaType<?> findDescriptor(Type javaType) {
97112
return descriptorsByTypeName.get( javaType.getTypeName() );
98113
}
99114

100-
public <J> JavaType<J> findDescriptor(Class<J> javaType) {
115+
public @Nullable JavaType<?> findDescriptor(int sqlTypeCode, Type javaType) {
116+
final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName =
117+
typeCodeSpecificDescriptorsByTypeName.get( sqlTypeCode );
118+
return descriptorsByTypeName.get( javaType.getTypeName() );
119+
}
120+
121+
public <J> @Nullable JavaType<J> findDescriptor(Class<J> javaType) {
101122
//noinspection unchecked
102123
return (JavaType<J>) findDescriptor( (Type) javaType );
103124
}
104125

126+
public <J> @Nullable JavaType<J> findDescriptor(int sqlTypeCode, Class<J> javaType) {
127+
//noinspection unchecked
128+
return (JavaType<J>) findDescriptor( sqlTypeCode, (Type) javaType );
129+
}
130+
105131
public <J> JavaType<J> resolveDescriptor(Class<? extends J> javaType, Supplier<JavaType<J>> creator) {
106132
//noinspection unchecked
107133
return (JavaType<J>) resolveDescriptor( javaType.getTypeName(), creator );
108134
}
109135

136+
public <J> JavaType<J> resolveDescriptor(int sqlTypeCode, Class<? extends J> javaType, Supplier<JavaType<J>> creator) {
137+
final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName =
138+
typeCodeSpecificDescriptorsByTypeName.computeIfAbsent( sqlTypeCode, k -> new ConcurrentHashMap<>() );
139+
//noinspection unchecked
140+
return (JavaType<J>) resolveDescriptor( descriptorsByTypeName, javaType.getTypeName(), creator );
141+
}
142+
143+
public JavaType<?> resolveDescriptor(int sqlTypeCode, Type javaType, Supplier<JavaType<?>> creator) {
144+
final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName =
145+
typeCodeSpecificDescriptorsByTypeName.computeIfAbsent( sqlTypeCode, k -> new ConcurrentHashMap<>() );
146+
return resolveDescriptor( descriptorsByTypeName, javaType.getTypeName(), creator );
147+
}
148+
110149
@Deprecated(since = "7.2", forRemoval = true) // Can be private
111150
private JavaType<?> resolveDescriptor(String javaTypeName, Supplier<? extends JavaType<?>> creator) {
151+
return resolveDescriptor( descriptorsByTypeName, javaTypeName, creator );
152+
}
153+
154+
private JavaType<?> resolveDescriptor(ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName, String javaTypeName, Supplier<? extends JavaType<?>> creator) {
112155
final var cached = descriptorsByTypeName.get( javaTypeName );
113156
if ( cached != null ) {
114157
return cached;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.mapping.basic;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.Table;
10+
import org.hibernate.annotations.JdbcTypeCode;
11+
import org.hibernate.cfg.AvailableSettings;
12+
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
13+
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
14+
import org.hibernate.persister.entity.EntityPersister;
15+
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.Jira;
18+
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
19+
import org.hibernate.testing.orm.junit.ServiceRegistry;
20+
import org.hibernate.testing.orm.junit.SessionFactory;
21+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
22+
import org.hibernate.testing.orm.junit.Setting;
23+
import org.hibernate.type.SqlTypes;
24+
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
25+
import org.hibernate.type.descriptor.jdbc.JdbcType;
26+
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
27+
import org.junit.jupiter.api.Test;
28+
29+
import java.util.List;
30+
31+
import static org.hamcrest.MatcherAssert.assertThat;
32+
import static org.hamcrest.Matchers.equalTo;
33+
import static org.hamcrest.Matchers.isA;
34+
35+
// It's vital that EntityWithJson is listed first to reproduce the bug,
36+
// the bug being, that JsonJavaType was registered in JavaTypeRegistry under e.g. List<Integer>,
37+
// which would then wrongly be used for EntityWithArray#listInteger as JavaType
38+
@DomainModel(annotatedClasses = { JsonAndArrayMappingTests.EntityWithJson.class, JsonAndArrayMappingTests.EntityWithArray.class})
39+
@SessionFactory
40+
@ServiceRegistry(settings = @Setting(name = AvailableSettings.JSON_FORMAT_MAPPER, value = "jackson"))
41+
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTypedArrays.class)
42+
@Jira("https://hibernate.atlassian.net/browse/HHH-17680")
43+
public class JsonAndArrayMappingTests {
44+
45+
@Test
46+
public void verifyMappings(SessionFactoryScope scope) {
47+
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
48+
.getRuntimeMetamodels()
49+
.getMappingMetamodel();
50+
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
51+
final JdbcType jsonType = jdbcTypeRegistry.getDescriptor( SqlTypes.JSON );
52+
final EntityPersister jsonEntity = mappingMetamodel.findEntityDescriptor( EntityWithJson.class );
53+
54+
final BasicAttributeMapping listStringJsonAttribute = (BasicAttributeMapping) jsonEntity.findAttributeMapping(
55+
"listString" );
56+
final BasicAttributeMapping listIntegerJsonAttribute = (BasicAttributeMapping) jsonEntity.findAttributeMapping(
57+
"listInteger" );
58+
59+
assertThat( listStringJsonAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
60+
assertThat( listIntegerJsonAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
61+
62+
assertThat( listStringJsonAttribute.getJdbcMapping().getJdbcType(), isA( (Class<JdbcType>) jsonType.getClass() ) );
63+
assertThat( listIntegerJsonAttribute.getJdbcMapping().getJdbcType(), isA( (Class<JdbcType>) jsonType.getClass() ) );
64+
65+
final EntityPersister arrayEntity = mappingMetamodel.findEntityDescriptor( EntityWithArray.class );
66+
67+
final BasicAttributeMapping listStringArrayAttribute = (BasicAttributeMapping) arrayEntity.findAttributeMapping(
68+
"listString" );
69+
final BasicAttributeMapping listIntegerArrayAttribute = (BasicAttributeMapping) arrayEntity.findAttributeMapping(
70+
"listInteger" );
71+
72+
assertThat( listStringArrayAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
73+
assertThat( listIntegerArrayAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
74+
75+
assertThat( listStringArrayAttribute.getJdbcMapping().getJdbcType(), isA( ArrayJdbcType.class ) );
76+
assertThat( listIntegerArrayAttribute.getJdbcMapping().getJdbcType(), isA( ArrayJdbcType.class ) );
77+
}
78+
79+
@Entity(name = "EntityWithJson")
80+
@Table(name = "EntityWithJson")
81+
public static class EntityWithJson {
82+
@Id
83+
private Integer id;
84+
85+
@JdbcTypeCode( SqlTypes.JSON )
86+
private List<String> listString;
87+
@JdbcTypeCode( SqlTypes.JSON )
88+
private List<Integer> listInteger;
89+
90+
public EntityWithJson() {
91+
}
92+
}
93+
94+
@Entity(name = "EntityWithArray")
95+
@Table(name = "EntityWithArray")
96+
public static class EntityWithArray {
97+
@Id
98+
private Integer id;
99+
100+
@JdbcTypeCode( SqlTypes.ARRAY )
101+
private List<String> listString;
102+
@JdbcTypeCode( SqlTypes.ARRAY )
103+
private List<Integer> listInteger;
104+
105+
public EntityWithArray() {
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)