HHH-17739 fix NPE for field with unsupported collection type

cleanups and warning fixes in TypeConfiguration and BasicValue
This commit is contained in:
Gavin King 2024-09-08 16:49:56 +02:00
parent f5e1d1cd73
commit 6c6c92e88d
5 changed files with 254 additions and 239 deletions

View File

@ -40,7 +40,6 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.Size;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
@ -82,6 +81,7 @@ import jakarta.persistence.EnumType;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static java.lang.Boolean.parseBoolean; import static java.lang.Boolean.parseBoolean;
import static org.hibernate.internal.util.ReflectHelper.reflectedPropertyType;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
import static org.hibernate.mapping.MappingHelper.injectParameters; import static org.hibernate.mapping.MappingHelper.injectParameters;
@ -103,6 +103,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
private Function<TypeConfiguration, java.lang.reflect.Type> implicitJavaTypeAccess; private Function<TypeConfiguration, java.lang.reflect.Type> implicitJavaTypeAccess;
private EnumType enumerationStyle; private EnumType enumerationStyle;
@SuppressWarnings("deprecation")
private TemporalType temporalPrecision; private TemporalType temporalPrecision;
private TimeZoneStorageType timeZoneStorageType; private TimeZoneStorageType timeZoneStorageType;
private boolean isSoftDelete; private boolean isSoftDelete;
@ -233,8 +234,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
@Override @Override
public long getColumnLength() { public long getColumnLength() {
final Selectable selectable = getColumn(); final Selectable selectable = getColumn();
if ( selectable instanceof Column ) { if ( selectable instanceof Column column ) {
final Column column = (Column) selectable;
final Long length = column.getLength(); final Long length = column.getLength();
return length == null ? NO_COLUMN_LENGTH : length; return length == null ? NO_COLUMN_LENGTH : length;
} }
@ -246,10 +246,10 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
@Override @Override
public int getColumnPrecision() { public int getColumnPrecision() {
final Selectable selectable = getColumn(); final Selectable selectable = getColumn();
if ( selectable instanceof Column ) { if ( selectable instanceof Column column ) {
final Column column = (Column) selectable; final Integer temporalPrecision = column.getTemporalPrecision();
if ( column.getTemporalPrecision() != null ) { if ( temporalPrecision != null ) {
return column.getTemporalPrecision(); return temporalPrecision;
} }
final Integer precision = column.getPrecision(); final Integer precision = column.getPrecision();
return precision == null ? NO_COLUMN_PRECISION : precision; return precision == null ? NO_COLUMN_PRECISION : precision;
@ -262,8 +262,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
@Override @Override
public int getColumnScale() { public int getColumnScale() {
final Selectable selectable = getColumn(); final Selectable selectable = getColumn();
if ( selectable instanceof Column ) { if ( selectable instanceof Column column ) {
final Column column = (Column) selectable;
final Integer scale = column.getScale(); final Integer scale = column.getScale();
return scale == null ? NO_COLUMN_SCALE : scale; return scale == null ? NO_COLUMN_SCALE : scale;
} }
@ -282,10 +281,10 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
@Override @Override
public void copyTypeFrom(SimpleValue sourceValue) { public void copyTypeFrom(SimpleValue sourceValue) {
super.copyTypeFrom( sourceValue ); super.copyTypeFrom( sourceValue );
if ( sourceValue instanceof BasicValue ) { if ( sourceValue instanceof BasicValue basicValue ) {
final BasicValue basicValue = (BasicValue) sourceValue; resolution = basicValue.resolution;
this.resolution = basicValue.resolution; implicitJavaTypeAccess =
this.implicitJavaTypeAccess = (typeConfiguration) -> basicValue.implicitJavaTypeAccess.apply( typeConfiguration ); typeConfiguration -> basicValue.implicitJavaTypeAccess.apply( typeConfiguration );
} }
} }
@ -446,55 +445,71 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
getBuildingContext() getBuildingContext()
); );
} }
else if ( isVersion() ) {
if ( isVersion() ) {
return VersionResolution.from( implicitJavaTypeAccess, timeZoneStorageType, getBuildingContext() ); return VersionResolution.from( implicitJavaTypeAccess, timeZoneStorageType, getBuildingContext() );
} }
else {
// determine JavaType if we can
final BasicJavaType<?> explicitJavaType = getExplicitJavaType();
final JavaType<?> javaType = determineJavaType( explicitJavaType );
final ConverterDescriptor converterDescriptor = getConverterDescriptor( javaType );
return converterDescriptor != null
? converterResolution( javaType, converterDescriptor )
: resolution( explicitJavaType, javaType );
}
}
// determine JavaType if we can private BasicJavaType<?> getExplicitJavaType() {
final BasicJavaType<?> explicitJavaType = explicitJavaTypeAccess == null return explicitJavaTypeAccess == null ? null
? null
: explicitJavaTypeAccess.apply( getTypeConfiguration() ); : explicitJavaTypeAccess.apply( getTypeConfiguration() );
}
JavaType<?> javaType = determineJavaType( explicitJavaType ); private ConverterDescriptor getConverterDescriptor(JavaType<?> javaType) {
ConverterDescriptor attributeConverterDescriptor = getAttributeConverterDescriptor(); final ConverterDescriptor converterDescriptor = getAttributeConverterDescriptor();
if ( isSoftDelete() ) { if ( isSoftDelete() ) {
assert attributeConverterDescriptor != null; assert converterDescriptor != null;
final boolean conversionWasUnspecified = SoftDelete.UnspecifiedConversion.class.equals( attributeConverterDescriptor.getAttributeConverterClass() ); final ConverterDescriptor softDeleteConverterDescriptor =
if ( conversionWasUnspecified ) { getSoftDeleteConverterDescriptor( converterDescriptor, javaType);
final JdbcType jdbcType = BooleanJdbcType.INSTANCE.resolveIndicatedType( this, javaType ); return getSoftDeleteStrategy() == SoftDeleteType.ACTIVE
if ( jdbcType.isNumber() ) { ? new ReversedConverterDescriptor<>( softDeleteConverterDescriptor )
attributeConverterDescriptor = new InstanceBasedConverterDescriptor( : softDeleteConverterDescriptor;
NumericBooleanConverter.INSTANCE, }
getBuildingContext().getBootstrapContext().getClassmateContext() else {
); return converterDescriptor;
} }
else if ( jdbcType.isString() ) { }
// here we pick 'T' / 'F' storage, though 'Y' / 'N' is equally valid - its 50/50
attributeConverterDescriptor = new InstanceBasedConverterDescriptor(
TrueFalseConverter.INSTANCE,
getBuildingContext().getBootstrapContext().getClassmateContext()
);
}
else {
// should indicate BIT or BOOLEAN == no conversion needed
// - we still create the converter to properly set up JDBC type, etc
attributeConverterDescriptor = new InstanceBasedConverterDescriptor(
PassThruSoftDeleteConverter.INSTANCE,
getBuildingContext().getBootstrapContext().getClassmateContext()
);
}
}
if ( getSoftDeleteStrategy() == SoftDeleteType.ACTIVE ) { private ConverterDescriptor getSoftDeleteConverterDescriptor(
attributeConverterDescriptor = new ReversedConverterDescriptor<>( attributeConverterDescriptor ); ConverterDescriptor attributeConverterDescriptor, JavaType<?> javaType) {
final boolean conversionWasUnspecified =
SoftDelete.UnspecifiedConversion.class.equals( attributeConverterDescriptor.getAttributeConverterClass() );
if ( conversionWasUnspecified ) {
final JdbcType jdbcType = BooleanJdbcType.INSTANCE.resolveIndicatedType( this, javaType);
if ( jdbcType.isNumber() ) {
return new InstanceBasedConverterDescriptor(
NumericBooleanConverter.INSTANCE,
getBuildingContext().getBootstrapContext().getClassmateContext()
);
}
else if ( jdbcType.isString() ) {
// here we pick 'T' / 'F' storage, though 'Y' / 'N' is equally valid - its 50/50
return new InstanceBasedConverterDescriptor(
TrueFalseConverter.INSTANCE,
getBuildingContext().getBootstrapContext().getClassmateContext()
);
}
else {
// should indicate BIT or BOOLEAN == no conversion needed
// - we still create the converter to properly set up JDBC type, etc
return new InstanceBasedConverterDescriptor(
PassThruSoftDeleteConverter.INSTANCE,
getBuildingContext().getBootstrapContext().getClassmateContext()
);
} }
} }
else {
return attributeConverterDescriptor != null return attributeConverterDescriptor;
? converterResolution( javaType, attributeConverterDescriptor ) }
: resolution( explicitJavaType, javaType );
} }
private static class ReversedConverterDescriptor<R> implements ConverterDescriptor { private static class ReversedConverterDescriptor<R> implements ConverterDescriptor {
@ -675,11 +690,10 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
getBuildingContext() getBuildingContext()
); );
if ( javaType instanceof BasicPluralJavaType<?> if ( javaType instanceof BasicPluralJavaType<?> containerJtd
&& !attributeConverterDescriptor.getDomainValueResolvedType().getErasedType() && !attributeConverterDescriptor.getDomainValueResolvedType().getErasedType()
.isAssignableFrom( javaType.getJavaTypeClass() ) ) { .isAssignableFrom( javaType.getJavaTypeClass() ) ) {
// In this case, the converter applies to the element of a BasicPluralJavaType // In this case, the converter applies to the element of a BasicPluralJavaType
final BasicPluralJavaType<?> containerJtd = (BasicPluralJavaType<?>) javaType;
final BasicType registeredElementType = converterResolution.getLegacyResolvedBasicType(); final BasicType registeredElementType = converterResolution.getLegacyResolvedBasicType();
final Selectable column = getColumn(); final Selectable column = getColumn();
final BasicType<?> registeredType = registeredElementType == null ? null final BasicType<?> registeredType = registeredElementType == null ? null
@ -729,66 +743,76 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
} }
private JavaType<?> determineReflectedJavaType() { private JavaType<?> determineReflectedJavaType() {
final java.lang.reflect.Type impliedJavaType;
final TypeConfiguration typeConfiguration = getTypeConfiguration(); final TypeConfiguration typeConfiguration = getTypeConfiguration();
final java.lang.reflect.Type impliedJavaType = impliedJavaType( typeConfiguration );
if ( impliedJavaType == null ) {
return null;
}
else {
resolvedJavaType = impliedJavaType;
return javaType( typeConfiguration, impliedJavaType );
}
}
private java.lang.reflect.Type impliedJavaType(TypeConfiguration typeConfiguration) {
if ( resolvedJavaType != null ) { if ( resolvedJavaType != null ) {
impliedJavaType = resolvedJavaType; return resolvedJavaType;
} }
else if ( implicitJavaTypeAccess != null ) { else if ( implicitJavaTypeAccess != null ) {
impliedJavaType = implicitJavaTypeAccess.apply( typeConfiguration ); return implicitJavaTypeAccess.apply(typeConfiguration);
} }
else if ( ownerName != null && propertyName != null ) { else if ( ownerName != null && propertyName != null ) {
impliedJavaType = ReflectHelper.reflectedPropertyType( return reflectedPropertyType( ownerName, propertyName,
ownerName, getServiceRegistry().requireService( ClassLoaderService.class ) );
propertyName,
getServiceRegistry().requireService( ClassLoaderService.class )
);
} }
else { else {
return null; return null;
} }
}
resolvedJavaType = impliedJavaType; private JavaType<Object> javaType(TypeConfiguration typeConfiguration, java.lang.reflect.Type impliedJavaType) {
final JavaType<Object> javaType = typeConfiguration.getJavaTypeRegistry().findDescriptor( impliedJavaType );
if ( impliedJavaType == null ) { return javaType == null ? specialJavaType( typeConfiguration, impliedJavaType ) : javaType;
return null; }
}
private JavaType<Object> specialJavaType(
TypeConfiguration typeConfiguration,
java.lang.reflect.Type impliedJavaType) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
final JavaType<Object> javaType = javaTypeRegistry.findDescriptor( impliedJavaType ); if ( jdbcTypeCode != null ) {
final MutabilityPlan<Object> explicitMutabilityPlan = explicitMutabilityPlanAccess != null // Construct special JavaType instances for JSON/XML types which can report recommended JDBC types
? explicitMutabilityPlanAccess.apply( typeConfiguration ) // and implement toString/fromString as well as copying based on FormatMapper operations
: null; switch ( jdbcTypeCode ) {
final MutabilityPlan<Object> determinedMutabilityPlan = explicitMutabilityPlan != null case SqlTypes.JSON:
final JavaType<Object> jsonJavaType =
new JsonJavaType<>( impliedJavaType,
mutabilityPlan( typeConfiguration, impliedJavaType ),
typeConfiguration );
javaTypeRegistry.addDescriptor( jsonJavaType );
return jsonJavaType;
case SqlTypes.SQLXML:
final JavaType<Object> xmlJavaType =
new XmlJavaType<>( impliedJavaType,
mutabilityPlan( typeConfiguration, impliedJavaType ),
typeConfiguration );
javaTypeRegistry.addDescriptor( xmlJavaType );
return xmlJavaType;
}
}
return javaTypeRegistry.resolveDescriptor( impliedJavaType );
}
private MutabilityPlan<Object> mutabilityPlan(
TypeConfiguration typeConfiguration, java.lang.reflect.Type impliedJavaType) {
final MutabilityPlan<Object> explicitMutabilityPlan = getExplicitMutabilityPlan();
return explicitMutabilityPlan != null
? explicitMutabilityPlan ? explicitMutabilityPlan
: RegistryHelper.INSTANCE.determineMutabilityPlan( impliedJavaType, typeConfiguration ); : 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 private MutabilityPlan<Object> getExplicitMutabilityPlan() {
// and implement toString/fromString as well as copying based on FormatMapper operations return explicitMutabilityPlanAccess == null ? null
switch ( jdbcTypeCode ) { : explicitMutabilityPlanAccess.apply( getTypeConfiguration() );
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( private static Resolution<?> interpretExplicitlyNamedType(
@ -1064,8 +1088,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
? FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( explicitCustomType ) ? FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( explicitCustomType )
: getUserTypeBean( explicitCustomType, properties ).getBeanInstance(); : getUserTypeBean( explicitCustomType, properties ).getBeanInstance();
if ( typeInstance instanceof TypeConfigurationAware ) { if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
final TypeConfigurationAware configurationAware = (TypeConfigurationAware) typeInstance;
configurationAware.setTypeConfiguration( getTypeConfiguration() ); configurationAware.setTypeConfiguration( getTypeConfiguration() );
} }
@ -1096,11 +1119,12 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
} }
} }
@SuppressWarnings("deprecation")
public void setTemporalPrecision(TemporalType temporalPrecision) { public void setTemporalPrecision(TemporalType temporalPrecision) {
this.temporalPrecision = temporalPrecision; this.temporalPrecision = temporalPrecision;
} }
@Override @Override @SuppressWarnings("deprecation")
public TemporalType getTemporalPrecision() { public TemporalType getTemporalPrecision() {
return temporalPrecision; return temporalPrecision;
} }

View File

@ -68,59 +68,46 @@ public class BasicTypeRegistry implements Serializable {
if ( typeReference == null ) { if ( typeReference == null ) {
return null; return null;
} }
if ( !name.equals( typeReference.getName() ) ) { else if ( !name.equals( typeReference.getName() ) ) {
final BasicType<?> basicType = typesByName.get( typeReference.getName() ); final BasicType<?> basicType = typesByName.get( typeReference.getName() );
if ( basicType != null ) { if ( basicType != null ) {
return basicType; return basicType;
} }
} }
final JavaType<Object> javaType = typeConfiguration.getJavaTypeRegistry().getDescriptor(
typeReference.getBindableJavaType() return createBasicType( name, typeReference );
); }
final JdbcType jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor(
typeReference.getSqlTypeCode() private BasicType<?> createBasicType(String name, BasicTypeReference<?> typeReference) {
); final JavaType<Object> javaType =
final BasicType<?> type; typeConfiguration.getJavaTypeRegistry()
.getDescriptor( typeReference.getBindableJavaType() );
final JdbcType jdbcType =
typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( typeReference.getSqlTypeCode() );
final BasicType<?> createdType = createBasicType( typeReference, javaType, jdbcType );
primeRegistryEntry( createdType );
typesByName.put( typeReference.getName(), createdType );
typesByName.put( name, createdType );
return createdType;
}
private static BasicType<?> createBasicType(
BasicTypeReference<?> typeReference, JavaType<Object> javaType, JdbcType jdbcType) {
final String name = typeReference.getName();
if ( typeReference.getConverter() == null ) { if ( typeReference.getConverter() == null ) {
if ( typeReference.isForceImmutable() ) { return typeReference.isForceImmutable()
type = new ImmutableNamedBasicTypeImpl<>( ? new ImmutableNamedBasicTypeImpl<>( javaType, jdbcType, name )
javaType, : new NamedBasicTypeImpl<>( javaType, jdbcType, name );
jdbcType,
typeReference.getName()
);
}
else {
type = new NamedBasicTypeImpl<>(
javaType,
jdbcType,
typeReference.getName()
);
}
} }
else { else {
//noinspection unchecked final BasicValueConverter<?, ?> converter = typeReference.getConverter();
final BasicValueConverter<Object, ?> converter = (BasicValueConverter<Object, ?>) typeReference.getConverter();
assert javaType == converter.getDomainJavaType(); assert javaType == converter.getDomainJavaType();
if ( typeReference.isForceImmutable() ) { return typeReference.isForceImmutable()
type = new CustomMutabilityConvertedBasicTypeImpl<>( ? new CustomMutabilityConvertedBasicTypeImpl<>( name, jdbcType, converter,
typeReference.getName(), ImmutableMutabilityPlan.instance() )
jdbcType, : new ConvertedBasicTypeImpl<>( name, jdbcType, converter );
converter,
ImmutableMutabilityPlan.instance()
);
}
else {
type = new ConvertedBasicTypeImpl<>(
typeReference.getName(),
jdbcType,
converter
);
}
} }
primeRegistryEntry( type );
typesByName.put( typeReference.getName(), type );
typesByName.put( name, type );
return type;
} }
public <J> BasicType<J> getRegisteredType(java.lang.reflect.Type javaType) { public <J> BasicType<J> getRegisteredType(java.lang.reflect.Type javaType) {
@ -144,10 +131,7 @@ public class BasicTypeRegistry implements Serializable {
} }
public <J> BasicType<J> resolve(JavaType<J> javaType, int sqlTypeCode) { public <J> BasicType<J> resolve(JavaType<J> javaType, int sqlTypeCode) {
return resolve( return resolve( javaType, typeConfiguration.getJdbcTypeRegistry().getDescriptor( sqlTypeCode ) );
javaType,
typeConfiguration.getJdbcTypeRegistry().getDescriptor( sqlTypeCode )
);
} }
/** /**
@ -158,38 +142,37 @@ public class BasicTypeRegistry implements Serializable {
return resolve( return resolve(
javaType, javaType,
jdbcType, jdbcType,
() -> { () -> resolvedType( javaType, jdbcType )
if ( javaType instanceof BasicPluralJavaType<?> && jdbcType instanceof ArrayJdbcType ) {
//noinspection unchecked
final BasicPluralJavaType<Object> pluralJavaType = (BasicPluralJavaType<Object>) javaType;
final BasicType<Object> elementType = resolve(
pluralJavaType.getElementJavaType(),
( (ArrayJdbcType) jdbcType ).getElementJdbcType()
);
final BasicType<?> resolvedType = pluralJavaType.resolveType(
typeConfiguration,
typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(),
elementType,
null,
typeConfiguration.getCurrentBaseSqlTypeIndicators()
);
if ( resolvedType instanceof BasicPluralType<?, ?> ) {
register( resolvedType );
}
//noinspection unchecked
return (BasicType<J>) resolvedType;
}
return new BasicTypeImpl<>( javaType, jdbcType );
}
); );
} }
public <J> BasicType<J> resolve(JavaType<J> javaType, JdbcType jdbcType, String baseTypeName) { private <J> BasicType<J> resolvedType(JavaType<J> javaType, JdbcType jdbcType) {
return resolve( if ( javaType instanceof BasicPluralJavaType<?> pluralJavaType
javaType, && jdbcType instanceof ArrayJdbcType arrayType ) {
jdbcType, //noinspection unchecked
() -> new NamedBasicTypeImpl<>( javaType, jdbcType, baseTypeName ) return (BasicType<J>) resolvedType( arrayType, pluralJavaType );
}
else {
return new BasicTypeImpl<>( javaType, jdbcType );
}
}
private <E> BasicType<?> resolvedType(ArrayJdbcType arrayType, BasicPluralJavaType<E> castPluralJavaType) {
final BasicType<?> resolvedType = castPluralJavaType.resolveType(
typeConfiguration,
typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect(),
resolve( castPluralJavaType.getElementJavaType(), arrayType.getElementJdbcType() ),
null,
typeConfiguration.getCurrentBaseSqlTypeIndicators()
); );
if ( resolvedType instanceof BasicPluralType<?,?> ) {
register( resolvedType );
}
return resolvedType;
}
public <J> BasicType<J> resolve(JavaType<J> javaType, JdbcType jdbcType, String baseTypeName) {
return resolve( javaType, jdbcType, () -> new NamedBasicTypeImpl<>( javaType, jdbcType, baseTypeName ) );
} }
/** /**
@ -197,34 +180,48 @@ public class BasicTypeRegistry implements Serializable {
* JdbcType combo or create (and register) one. * JdbcType combo or create (and register) one.
*/ */
public <J> BasicType<J> resolve(JavaType<J> javaType, JdbcType jdbcType, Supplier<BasicType<J>> creator) { public <J> BasicType<J> resolve(JavaType<J> javaType, JdbcType jdbcType, Supplier<BasicType<J>> creator) {
final Map<JavaType<?>, BasicType<?>> typeByJavaTypeForJdbcType = registryValues.computeIfAbsent( final BasicType<?> registeredBasicType = registryForJdbcType( jdbcType ).get( javaType );
jdbcType, //noinspection unchecked
key -> new ConcurrentHashMap<>() return registeredBasicType != null
); ? (BasicType<J>) registeredBasicType
: createIfUnregistered( javaType, jdbcType, creator );
}
final BasicType<?> foundBasicType = typeByJavaTypeForJdbcType.get( javaType ); private <J> BasicType<J> createIfUnregistered(
if ( foundBasicType != null ) { JavaType<J> javaType,
//noinspection unchecked JdbcType jdbcType,
return (BasicType<J>) foundBasicType; Supplier<BasicType<J>> creator) {
}
// Before simply creating the type, we try to find if there is a registered type for this java type, // Before simply creating the type, we try to find if there is a registered type for this java type,
// and if so, if the jdbc type descriptor matches. Unless it does, we at least reuse the name // and if so, if the jdbc type descriptor matches. Unless it does, we at least reuse the name
final BasicType<J> registeredType = getRegisteredType( javaType.getJavaType() ); final BasicType<J> registeredType = getRegisteredType( javaType.getJavaType() );
if ( registeredType != null && registeredType.getJdbcType() == jdbcType && registeredType.getMappedJavaType() == javaType ) { if ( registeredTypeMatches( javaType, jdbcType, registeredType ) ) {
return registeredType; return registeredType;
} }
final BasicType<J> createdBasicType = creator.get(); else {
typeByJavaTypeForJdbcType.put( javaType, createdBasicType ); final BasicType<J> createdType = creator.get();
register( javaType, jdbcType, createdType );
return createdType;
}
}
// if we are still building mappings, register this ad-hoc type private static <J> boolean registeredTypeMatches(JavaType<J> javaType, JdbcType jdbcType, BasicType<J> registeredType) {
// via a unique code. this is to support envers return registeredType != null
try { && registeredType.getJdbcType() == jdbcType
typeConfiguration.getMetadataBuildingContext().getBootstrapContext() && registeredType.getMappedJavaType() == javaType;
.registerAdHocBasicType( createdBasicType ); }
private <J> void register(JavaType<J> javaType, JdbcType jdbcType, BasicType<J> createdType) {
if ( createdType != null ) {
registryForJdbcType( jdbcType ).put( javaType, createdType );
// if we are still building mappings, register this adhoc
// type via a unique code. (This is to support Envers.)
try {
typeConfiguration.getMetadataBuildingContext().getBootstrapContext()
.registerAdHocBasicType( createdType );
}
catch (Exception ignore) {
}
} }
catch (Exception ignore) {
}
return createdBasicType;
} }
@ -260,16 +257,12 @@ public class BasicTypeRegistry implements Serializable {
} }
private void applyOrOverwriteEntry(BasicType<?> type) { private void applyOrOverwriteEntry(BasicType<?> type) {
final Map<JavaType<?>, BasicType<?>> typeByJavaTypeForJdbcType = registryValues.computeIfAbsent( final JdbcType jdbcType = type.getJdbcType();
type.getJdbcType(), final BasicType<?> existing = registryForJdbcType( jdbcType ).put( type.getMappedJavaType(), type );
jdbcType -> new ConcurrentHashMap<>()
);
final BasicType<?> existing = typeByJavaTypeForJdbcType.put( type.getMappedJavaType(), type );
if ( existing != null ) { if ( existing != null ) {
LOG.debugf( LOG.debugf(
"BasicTypeRegistry registration overwritten (%s + %s); previous =`%s`", "BasicTypeRegistry registration overwritten (%s + %s); previous =`%s`",
type.getJdbcType().getFriendlyName(), jdbcType.getFriendlyName(),
type.getJavaTypeDescriptor(), type.getJavaTypeDescriptor(),
existing existing
); );
@ -360,26 +353,25 @@ public class BasicTypeRegistry implements Serializable {
} }
private void primeRegistryEntry(BasicType<?> type) { private void primeRegistryEntry(BasicType<?> type) {
final Map<JavaType<?>, BasicType<?>> typeByJavaTypeForJdbcType = registryValues.computeIfAbsent( final JdbcType jdbcType = type.getJdbcType();
type.getJdbcType(), final BasicType<?> existing = registryForJdbcType( jdbcType ).get( type.getMappedJavaType() );
jdbcType -> new ConcurrentHashMap<>()
);
final BasicType<?> existing = typeByJavaTypeForJdbcType.get( type.getMappedJavaType() );
if ( existing != null ) { if ( existing != null ) {
LOG.debugf( LOG.debugf(
"Skipping registration of BasicType (%s + %s); still priming. existing = %s", "Skipping registration of BasicType (%s + %s); still priming. existing = %s",
type.getJdbcType().getFriendlyName(), jdbcType.getFriendlyName(),
type.getJavaTypeDescriptor(), type.getJavaTypeDescriptor(),
existing existing
); );
} }
else { else {
typeByJavaTypeForJdbcType.put( type.getMappedJavaType(), type ); registryForJdbcType( jdbcType ).put( type.getMappedJavaType(), type );
} }
} }
private Map<JavaType<?>, BasicType<?>> registryForJdbcType(JdbcType jdbcType) {
return registryValues.computeIfAbsent( jdbcType, key -> new ConcurrentHashMap<>() );
}
private void applyRegistrationKeys(BasicType<?> type, String[] keys) { private void applyRegistrationKeys(BasicType<?> type, String[] keys) {
for ( String key : keys ) { for ( String key : keys ) {
// be safe... // be safe...

View File

@ -19,7 +19,7 @@ import org.hibernate.type.spi.TypeConfiguration;
* Descriptor for a basic plural Java type. * Descriptor for a basic plural Java type.
* A basic plural type represents a type, that is mapped to a single column instead of multiple rows. * A basic plural type represents a type, that is mapped to a single column instead of multiple rows.
* This is used for array or collection types, that are backed by e.g. SQL array or JSON/XML DDL types. * This is used for array or collection types, that are backed by e.g. SQL array or JSON/XML DDL types.
* * <p>
* The interface can be implemented by a plural java type e.g. {@link org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType} * The interface can be implemented by a plural java type e.g. {@link org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType}
* and provides access to the element java type, as well as a hook to resolve the {@link BasicType} based on the element {@link BasicType}, * and provides access to the element java type, as well as a hook to resolve the {@link BasicType} based on the element {@link BasicType},
* in order to gain enough information to implement storage and retrieval of the composite data type via JDBC. * in order to gain enough information to implement storage and retrieval of the composite data type via JDBC.

View File

@ -387,8 +387,7 @@ public class BasicCollectionJavaType<C extends Collection<E>, E> extends Abstrac
} }
} }
if ( value instanceof Object[] ) { if ( value instanceof Object[] raw ) {
final Object[] raw = (Object[]) value;
final C wrapped = semantics.instantiateRaw( raw.length, null ); final C wrapped = semantics.instantiateRaw( raw.length, null );
if ( componentJavaType.getJavaTypeClass().isAssignableFrom( value.getClass().getComponentType() ) ) { if ( componentJavaType.getJavaTypeClass().isAssignableFrom( value.getClass().getComponentType() ) ) {
for ( Object o : raw ) { for ( Object o : raw ) {

View File

@ -7,6 +7,7 @@
package org.hibernate.type.spi; package org.hibernate.type.spi;
import java.io.InvalidObjectException; import java.io.InvalidObjectException;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -32,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Function; import java.util.function.Function;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.Internal; import org.hibernate.Internal;
@ -499,7 +501,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return sessionFactory.getServiceRegistry(); return sessionFactory.getServiceRegistry();
} }
else { else {
return null; throw new AssertionFailure( "No service registry available" );
} }
} }
@ -574,6 +576,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Custom serialization hook // Custom serialization hook
@Serial
private Object readResolve() throws InvalidObjectException { private Object readResolve() throws InvalidObjectException {
if ( sessionFactory == null ) { if ( sessionFactory == null ) {
if ( sessionFactoryName != null || sessionFactoryUuid != null ) { if ( sessionFactoryName != null || sessionFactoryUuid != null ) {
@ -633,7 +636,8 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return Arrays.equals( components, ((ArrayCacheKey) o).components ); return o instanceof ArrayCacheKey key
&& Arrays.equals( components, key.components );
} }
@Override @Override
@ -799,6 +803,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
); );
} }
@SuppressWarnings("deprecation")
public TemporalType getSqlTemporalType(SqmExpressible<?> type) { public TemporalType getSqlTemporalType(SqmExpressible<?> type) {
if ( type == null ) { if ( type == null ) {
return null; return null;
@ -806,15 +811,18 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return getSqlTemporalType( type.getRelationalJavaType().getRecommendedJdbcType( getCurrentBaseSqlTypeIndicators() ) ); return getSqlTemporalType( type.getRelationalJavaType().getRecommendedJdbcType( getCurrentBaseSqlTypeIndicators() ) );
} }
@SuppressWarnings("deprecation")
public static TemporalType getSqlTemporalType(JdbcMapping jdbcMapping) { public static TemporalType getSqlTemporalType(JdbcMapping jdbcMapping) {
return getSqlTemporalType( jdbcMapping.getJdbcType() ); return getSqlTemporalType( jdbcMapping.getJdbcType() );
} }
@SuppressWarnings("deprecation")
public static TemporalType getSqlTemporalType(JdbcMappingContainer jdbcMappings) { public static TemporalType getSqlTemporalType(JdbcMappingContainer jdbcMappings) {
assert jdbcMappings.getJdbcTypeCount() == 1; assert jdbcMappings.getJdbcTypeCount() == 1;
return getSqlTemporalType( jdbcMappings.getSingleJdbcMapping().getJdbcType() ); return getSqlTemporalType( jdbcMappings.getSingleJdbcMapping().getJdbcType() );
} }
@SuppressWarnings("deprecation")
public static TemporalType getSqlTemporalType(MappingModelExpressible<?> type) { public static TemporalType getSqlTemporalType(MappingModelExpressible<?> type) {
if ( type instanceof BasicValuedMapping basicValuedMapping ) { if ( type instanceof BasicValuedMapping basicValuedMapping ) {
return getSqlTemporalType( basicValuedMapping.getJdbcMapping().getJdbcType() ); return getSqlTemporalType( basicValuedMapping.getJdbcMapping().getJdbcType() );
@ -838,25 +846,22 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
} }
} }
@SuppressWarnings("deprecation")
public static TemporalType getSqlTemporalType(JdbcType descriptor) { public static TemporalType getSqlTemporalType(JdbcType descriptor) {
return getSqlTemporalType( descriptor.getDefaultSqlTypeCode() ); return getSqlTemporalType( descriptor.getDefaultSqlTypeCode() );
} }
@SuppressWarnings("deprecation")
protected static TemporalType getSqlTemporalType(int jdbcTypeCode) { protected static TemporalType getSqlTemporalType(int jdbcTypeCode) {
switch ( jdbcTypeCode ) { return switch ( jdbcTypeCode ) {
case SqlTypes.TIMESTAMP: case SqlTypes.TIMESTAMP, SqlTypes.TIMESTAMP_WITH_TIMEZONE, SqlTypes.TIMESTAMP_UTC
case SqlTypes.TIMESTAMP_WITH_TIMEZONE: -> TemporalType.TIMESTAMP;
case SqlTypes.TIMESTAMP_UTC: case SqlTypes.TIME, SqlTypes.TIME_WITH_TIMEZONE, SqlTypes.TIME_UTC
return TemporalType.TIMESTAMP; -> TemporalType.TIME;
case SqlTypes.TIME: case SqlTypes.DATE
case SqlTypes.TIME_WITH_TIMEZONE: -> TemporalType.DATE;
case SqlTypes.TIME_UTC: default -> null;
return TemporalType.TIME; };
case SqlTypes.DATE:
return TemporalType.DATE;
default:
return null;
}
} }
public static IntervalType getSqlIntervalType(JdbcMappingContainer jdbcMappings) { public static IntervalType getSqlIntervalType(JdbcMappingContainer jdbcMappings) {
@ -869,12 +874,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
} }
protected static IntervalType getSqlIntervalType(int jdbcTypeCode) { protected static IntervalType getSqlIntervalType(int jdbcTypeCode) {
switch ( jdbcTypeCode ) { return jdbcTypeCode == SqlTypes.INTERVAL_SECOND ? IntervalType.SECOND : null;
case SqlTypes.INTERVAL_SECOND:
return IntervalType.SECOND;
default:
return null;
}
} }
public static boolean isJdbcTemporalType(SqmExpressible<?> type) { public static boolean isJdbcTemporalType(SqmExpressible<?> type) {