diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index bd506fef0b..2373246a83 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -65,7 +65,7 @@ import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.XmlJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; @@ -268,7 +268,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { typeContributions.getTypeConfiguration().getJdbcTypeRegistry().addDescriptor( Types.TINYINT, - SmallIntJdbcType.INSTANCE + TinyIntAsSmallIntJdbcType.INSTANCE ); typeContributions.contributeJdbcType( XmlJdbcType.INSTANCE ); typeContributions.contributeJdbcType( UUIDJdbcType.INSTANCE ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java index fc8b471567..9f07ef6d5f 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java @@ -56,7 +56,7 @@ import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import jakarta.persistence.TemporalType; @@ -103,10 +103,6 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect { if ( precision == 19 && scale == 0 ) { return jdbcTypeRegistry.getDescriptor( Types.BIGINT ); } - case Types.TINYINT: - if ( jtdsDriver ) { - return jdbcTypeRegistry.getDescriptor( Types.SMALLINT ); - } } return super.resolveSqlTypeDescriptor( columnTypeName, @@ -169,7 +165,7 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); if ( jtdsDriver ) { - jdbcTypeRegistry.addDescriptor( Types.TINYINT, SmallIntJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE ); // The jTDS driver doesn't support the JDBC4 signatures using 'long length' for stream bindings jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueResolution.java index 9a6153c59e..ac903ff69e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/EnumeratedValueResolution.java @@ -31,8 +31,10 @@ import org.hibernate.type.spi.TypeConfiguration; import jakarta.persistence.EnumType; +import static org.hibernate.type.SqlTypes.CHAR; import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.VARCHAR; /** * Resolution for {@linkplain Enum enum} mappings using {@link jakarta.persistence.Enumerated}, @@ -138,20 +140,20 @@ public class EnumeratedValueResolution,R> implements BasicValu if ( style == EnumType.ORDINAL ) { jdbcType = jdbcTypeRegistry.getDescriptor( enumJavaType.hasManyValues() ? SMALLINT : TINYINT ); - final JavaType jdbcJavaType = javaTypeRegistry.getDescriptor( Integer.class ); + final JavaType jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( + jdbcTypeIndicators.getColumnPrecision(), + jdbcTypeIndicators.getColumnScale(), + typeConfiguration + ); converter = new OrdinalEnumValueConverter<>( enumJavaType, jdbcType, jdbcJavaType ); } else if ( style == EnumType.STRING ) { - //noinspection rawtypes - final JavaType jdbcJavaType; - if ( jdbcTypeIndicators.getColumnLength() == 1 ) { - jdbcJavaType = javaTypeRegistry.getDescriptor( Character.class ); - } - else { - jdbcJavaType = javaTypeRegistry.getDescriptor( String.class ); - } - jdbcType = jdbcJavaType.getRecommendedJdbcType( jdbcTypeIndicators ); - //noinspection unchecked,rawtypes + jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeIndicators.getColumnLength() == 1 ? CHAR : VARCHAR ); + final JavaType jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping( + jdbcTypeIndicators.getColumnPrecision(), + jdbcTypeIndicators.getColumnScale(), + typeConfiguration + ); converter = new NamedEnumValueConverter<>( enumJavaType, jdbcType, jdbcJavaType ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java index a2e858d069..38f82b4efc 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java @@ -374,8 +374,8 @@ public class InferredBasicValueResolver { BasicJavaType explicitJavaType, JdbcType explicitJdbcType, MetadataBuildingContext context) { - final JavaType relationalJavaType = ordinalJavaType( explicitJavaType, context ); final JdbcType jdbcType = ordinalJdbcType( explicitJdbcType, enumJavaType, context ); + final JavaType relationalJavaType = ordinalJavaType( explicitJavaType, jdbcType, context ); return new EnumeratedValueResolution<>( jdbcType, @@ -395,6 +395,7 @@ public class InferredBasicValueResolver { private static JavaType ordinalJavaType( JavaType explicitJavaType, + JdbcType jdbcType, MetadataBuildingContext context) { if ( explicitJavaType != null ) { if ( !Integer.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) { @@ -407,7 +408,11 @@ public class InferredBasicValueResolver { return explicitJavaType; } else { - return context.getMetadataCollector().getTypeConfiguration().getJavaTypeRegistry().getDescriptor( Integer.class ); + return jdbcType.getJdbcRecommendedJavaTypeMapping( + null, + null, + context.getMetadataCollector().getTypeConfiguration() + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index eebb2dada7..b9c29fb825 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -113,7 +113,7 @@ import org.hibernate.type.descriptor.jdbc.NCharJdbcType; import org.hibernate.type.descriptor.jdbc.NClobJdbcType; import org.hibernate.type.descriptor.jdbc.NVarcharJdbcType; import org.hibernate.type.descriptor.jdbc.NumericJdbcType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; @@ -1044,7 +1044,7 @@ public abstract class AbstractHANADialect extends Dialect { jdbcTypeRegistry.addDescriptor( Types.NCLOB, this.nClobTypeDescriptor ); jdbcTypeRegistry.addDescriptor( Types.BLOB, this.blobTypeDescriptor ); // tinyint is unsigned on HANA - jdbcTypeRegistry.addDescriptor( Types.TINYINT, SmallIntJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE ); if ( isUseUnicodeStringTypes() ) { jdbcTypeRegistry.addDescriptor( Types.VARCHAR, NVarcharJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( Types.CHAR, NCharJdbcType.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 8cc39286f2..bcf1a84d88 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -73,7 +73,7 @@ import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.XmlJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; @@ -274,7 +274,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { typeContributions.getTypeConfiguration().getJdbcTypeRegistry().addDescriptor( Types.TINYINT, - SmallIntJdbcType.INSTANCE + TinyIntAsSmallIntJdbcType.INSTANCE ); typeContributions.contributeJdbcType( XmlJdbcType.INSTANCE ); typeContributions.contributeJdbcType( UUIDJdbcType.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index d7e299302a..1804e57a3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -53,7 +53,7 @@ import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import jakarta.persistence.TemporalType; @@ -107,10 +107,6 @@ public class SybaseDialect extends AbstractTransactSQLDialect { if ( precision == 19 && scale == 0 ) { return jdbcTypeRegistry.getDescriptor( Types.BIGINT ); } - case Types.TINYINT: - if ( jtdsDriver ) { - return jdbcTypeRegistry.getDescriptor( Types.SMALLINT ); - } } return super.resolveSqlTypeDescriptor( columnTypeName, @@ -173,7 +169,7 @@ public class SybaseDialect extends AbstractTransactSQLDialect { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); if ( jtdsDriver ) { - jdbcTypeRegistry.addDescriptor( Types.TINYINT, SmallIntJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE ); // The jTDS driver doesn't support the JDBC4 signatures using 'long length' for stream bindings jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index 18e6ed2bd0..9e829df432 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -22,7 +22,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; @@ -292,8 +292,8 @@ public class SqmUtil { if ( domainParamBinding.getType() instanceof JdbcMapping ) { jdbcMapping = (JdbcMapping) domainParamBinding.getType(); } - else if ( domainParamBinding.getBindType() instanceof BasicValuedModelPart ) { - jdbcMapping = ( (BasicValuedModelPart) domainParamBinding.getType() ).getJdbcMapping(); + else if ( domainParamBinding.getBindType() instanceof BasicValuedMapping ) { + jdbcMapping = ( (BasicValuedMapping) domainParamBinding.getType() ).getJdbcMapping(); } else { jdbcMapping = null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 496e7a2584..23cd1f0795 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -4948,10 +4948,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base final Object value = literal.getLiteralValue(); final Object sqlLiteralValue; // For converted query literals, we support both, the domain and relational java type - if ( value == null || valueConverter.getDomainJavaType().getJavaTypeClass().isInstance( value ) ) { + if ( value == null || valueConverter.getDomainJavaType().isInstance( value ) ) { sqlLiteralValue = valueConverter.toRelationalValue( value ); } - else if ( valueConverter.getRelationalJavaType().getJavaTypeClass().isInstance( value ) ) { + else if ( valueConverter.getRelationalJavaType().isInstance( value ) ) { sqlLiteralValue = value; } else if ( basicValuedMapping instanceof EntityDiscriminatorMapping ) { @@ -4963,6 +4963,15 @@ public abstract class BaseSqmToSqlAstConverter extends Base creationContext.getSessionFactory().getWrapperOptions() ); } + // In HQL, number literals might not match the relational java type exactly, + // so we allow coercion between the number types + else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() ) + && value instanceof Number ) { + sqlLiteralValue = valueConverter.getRelationalJavaType().coerce( + value, + creationContext.getSessionFactory()::getTypeConfiguration + ); + } else { throw new SqlTreeCreationException( String.format( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcParameterBindingsImpl.java index 1b9e7a8840..8c650be5d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcParameterBindingsImpl.java @@ -16,6 +16,7 @@ import java.util.function.BiConsumer; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.BindableType; import org.hibernate.query.spi.QueryParameterBinding; @@ -28,6 +29,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.type.BasicTypeReference; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; /** * Standard implementation of JdbcParameterBindings @@ -77,35 +79,62 @@ public class JdbcParameterBindingsImpl implements JdbcParameterBindings { throw new IllegalArgumentException( "Could not resolve NativeQuery parameter type : `" + param + "`"); } + final BasicValueConverter valueConverter = jdbcMapping == null ? null : jdbcMapping.getValueConverter(); + if ( binding.isMultiValued() ) { final Collection bindValues = binding.getBindValues(); final int bindValueCount = bindValues.size(); - Object lastBindValue = null; - for ( Object bindValue : bindValues ) { - final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); - jdbcParameterBinders.add( jdbcParameter ); - addBinding( jdbcParameter, new JdbcParameterBindingImpl( jdbcMapping, bindValue ) ); - lastBindValue = bindValue; - } final int bindValueMaxCount = NativeQueryImpl.determineBindValueMaxCount( paddingEnabled, inExprLimit, bindValueCount ); - if ( bindValueMaxCount != bindValueCount ) { - for ( int i = bindValueCount; i < bindValueMaxCount; i++ ) { + Object lastBindValue = null; + if ( valueConverter != null ) { + for ( Object bindValue : bindValues ) { final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); jdbcParameterBinders.add( jdbcParameter ); + lastBindValue = bindValue == null ? null : valueConverter.toRelationalValue( bindValue ); addBinding( jdbcParameter, new JdbcParameterBindingImpl( jdbcMapping, lastBindValue ) ); } + if ( bindValueMaxCount != bindValueCount ) { + for ( int i = bindValueCount; i < bindValueMaxCount; i++ ) { + final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); + jdbcParameterBinders.add( jdbcParameter ); + addBinding( jdbcParameter, new JdbcParameterBindingImpl( jdbcMapping, lastBindValue ) ); + } + } + } + else { + for ( Object bindValue : bindValues ) { + final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); + jdbcParameterBinders.add( jdbcParameter ); + addBinding( jdbcParameter, new JdbcParameterBindingImpl( jdbcMapping, bindValue ) ); + lastBindValue = bindValue; + } + if ( bindValueMaxCount != bindValueCount ) { + for ( int i = bindValueCount; i < bindValueMaxCount; i++ ) { + final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); + jdbcParameterBinders.add( jdbcParameter ); + addBinding( jdbcParameter, new JdbcParameterBindingImpl( jdbcMapping, lastBindValue ) ); + } + } } } else { + final Object bindValue; + if ( valueConverter != null && binding.getBindValue() != null ) { + bindValue = valueConverter.toRelationalValue( binding.getBindValue() ); + } + else { + bindValue = binding.getBindValue(); + } + final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl( jdbcMapping ); jdbcParameterBinders.add( jdbcParameter ); addBinding( jdbcParameter, - new JdbcParameterBindingImpl( jdbcMapping, binding.getBindValue() ) + new JdbcParameterBindingImpl( jdbcMapping, bindValue ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index 6b159d3f72..f5f2d7ebf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -87,7 +87,7 @@ public abstract class AbstractStandardBasicType } @Override - public JdbcLiteralFormatter getJdbcLiteralFormatter() { + public JdbcLiteralFormatter getJdbcLiteralFormatter() { return jdbcLiteralFormatter; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java index f26d972f40..32eec25fb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicArrayType.java @@ -6,22 +6,9 @@ */ package org.hibernate.type; -import java.lang.reflect.Array; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.ValueExtractor; -import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; -import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterArray; -import org.hibernate.type.spi.TypeConfiguration; /** * A type that maps between {@link java.sql.Types#ARRAY ARRAY} and {@code T[]} @@ -35,118 +22,11 @@ public class BasicArrayType private final BasicType baseDescriptor; private final String name; - private final ValueBinder jdbcValueBinder; - private final ValueExtractor jdbcValueExtractor; - private final JdbcLiteralFormatter jdbcLiteralFormatter; public BasicArrayType(BasicType baseDescriptor, JdbcType arrayJdbcType, JavaType arrayTypeDescriptor) { super( arrayJdbcType, arrayTypeDescriptor ); this.baseDescriptor = baseDescriptor; this.name = baseDescriptor.getName() + "[]"; - final ValueBinder jdbcValueBinder = super.getJdbcValueBinder(); - final ValueExtractor jdbcValueExtractor = super.getJdbcValueExtractor(); - final JdbcLiteralFormatter jdbcLiteralFormatter = super.getJdbcLiteralFormatter(); - //noinspection unchecked - final BasicValueConverter valueConverter = (BasicValueConverter) baseDescriptor.getValueConverter(); - if ( valueConverter != null ) { - this.jdbcValueBinder = new ValueBinder<>() { - @Override - public void bind(PreparedStatement st, T[] value, int index, WrapperOptions options) - throws SQLException { - jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), index, options ); - } - - @Override - public void bind(CallableStatement st, T[] value, String name, WrapperOptions options) - throws SQLException { - jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), name, options ); - } - - private T[] getValue( - T[] value, - BasicValueConverter valueConverter, - WrapperOptions options) { - if ( value == null ) { - return null; - } - final JdbcType elementJdbcType = baseDescriptor.getJdbcType(); - final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration(); - final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry() - .getDescriptor( elementJdbcType.getDefaultSqlTypeCode() ); - final Class preferredJavaTypeClass = underlyingJdbcType.getPreferredJavaTypeClass( options ); - final Class elementJdbcJavaTypeClass; - if ( preferredJavaTypeClass == null ) { - elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping( - null, - null, - typeConfiguration - ).getJavaTypeClass(); - } - else { - elementJdbcJavaTypeClass = preferredJavaTypeClass; - } - - if ( value.getClass().getComponentType() == elementJdbcJavaTypeClass ) { - return value; - } - final Object[] array = (Object[]) Array.newInstance( elementJdbcJavaTypeClass, value.length ); - for ( int i = 0; i < value.length; i++ ) { - array[i] = valueConverter.getRelationalJavaType().unwrap( - valueConverter.toRelationalValue( value[i] ), - elementJdbcJavaTypeClass, - options - ); - } - //noinspection unchecked - return (T[]) array; - } - }; - this.jdbcValueExtractor = new ValueExtractor() { - @Override - public T[] extract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - return getValue( jdbcValueExtractor.extract( rs, paramIndex, options ), valueConverter ); - } - - @Override - public T[] extract(CallableStatement statement, int paramIndex, WrapperOptions options) - throws SQLException { - return getValue( jdbcValueExtractor.extract( statement, paramIndex, options ), valueConverter ); - } - - @Override - public T[] extract(CallableStatement statement, String paramName, WrapperOptions options) - throws SQLException { - return getValue( jdbcValueExtractor.extract( statement, paramName, options ), valueConverter ); - } - - private T[] getValue(T[] value, BasicValueConverter valueConverter) { - if ( value == null ) { - return null; - } - if ( value.getClass().getComponentType() == valueConverter.getDomainJavaType().getJavaTypeClass() ) { - return value; - } - //noinspection unchecked - final T[] array = (T[]) Array.newInstance( - valueConverter.getDomainJavaType().getJavaTypeClass(), - value.length - ); - for ( int i = 0; i < value.length; i++ ) { - array[i] = valueConverter.toDomainValue( value[i] ); - } - return array; - } - }; - this.jdbcLiteralFormatter = new JdbcLiteralFormatterArray( - baseDescriptor.getJavaTypeDescriptor(), - jdbcLiteralFormatter - ); - } - else { - this.jdbcValueBinder = jdbcValueBinder; - this.jdbcValueExtractor = jdbcValueExtractor; - this.jdbcLiteralFormatter = jdbcLiteralFormatter; - } } @Override @@ -164,21 +44,6 @@ public class BasicArrayType return true; } - @Override - public ValueExtractor getJdbcValueExtractor() { - return jdbcValueExtractor; - } - - @Override - public ValueBinder getJdbcValueBinder() { - return jdbcValueBinder; - } - - @Override - public JdbcLiteralFormatter getJdbcLiteralFormatter() { - return jdbcLiteralFormatter; - } - @Override public BasicType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { // TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively? diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java index 7940546e1e..f2abed8720 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicCollectionType.java @@ -25,7 +25,10 @@ public class BasicCollectionType, E> private final BasicType baseDescriptor; private final String name; - public BasicCollectionType(BasicType baseDescriptor, JdbcType arrayJdbcType, BasicCollectionJavaType collectionTypeDescriptor) { + public BasicCollectionType( + BasicType baseDescriptor, + JdbcType arrayJdbcType, + BasicCollectionJavaType collectionTypeDescriptor) { super( arrayJdbcType, collectionTypeDescriptor ); this.baseDescriptor = baseDescriptor; this.name = determineName( collectionTypeDescriptor, baseDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java new file mode 100644 index 0000000000..a979aac153 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicArrayType.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ +package org.hibernate.type; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * A converted basic array type. + * + * @author Christian Beikov + */ +public class ConvertedBasicArrayType extends BasicArrayType { + + private final BasicValueConverter converter; + private final ValueExtractor jdbcValueExtractor; + private final ValueBinder jdbcValueBinder; + private final JdbcLiteralFormatter jdbcLiteralFormatter; + + @SuppressWarnings("unchecked") + public ConvertedBasicArrayType( + BasicType baseDescriptor, + JdbcType arrayJdbcType, + JavaType arrayTypeDescriptor, + BasicValueConverter converter) { + super( baseDescriptor, arrayJdbcType, arrayTypeDescriptor ); + this.converter = converter; + this.jdbcValueBinder = (ValueBinder) arrayJdbcType.getBinder( converter.getRelationalJavaType() ); + this.jdbcValueExtractor = (ValueExtractor) arrayJdbcType.getExtractor( converter.getRelationalJavaType() ); + this.jdbcLiteralFormatter = (JdbcLiteralFormatter) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() ); + } + + @Override + public BasicValueConverter getValueConverter() { + return converter; + } + + @Override + public JavaType getJdbcJavaType() { + return converter.getRelationalJavaType(); + } + + @Override + public ValueExtractor getJdbcValueExtractor() { + return jdbcValueExtractor; + } + + @Override + public ValueBinder getJdbcValueBinder() { + return jdbcValueBinder; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter() { + return jdbcLiteralFormatter; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicCollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicCollectionType.java new file mode 100644 index 0000000000..c02e9a04aa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/ConvertedBasicCollectionType.java @@ -0,0 +1,68 @@ +/* + * 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 . + */ +package org.hibernate.type; + +import java.util.Collection; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * A converted basic array type. + * + * @author Christian Beikov + */ +public class ConvertedBasicCollectionType, E> extends BasicCollectionType { + + private final BasicValueConverter converter; + private final ValueExtractor jdbcValueExtractor; + private final ValueBinder jdbcValueBinder; + private final JdbcLiteralFormatter jdbcLiteralFormatter; + + @SuppressWarnings("unchecked") + public ConvertedBasicCollectionType( + BasicType baseDescriptor, + JdbcType arrayJdbcType, + BasicCollectionJavaType arrayTypeDescriptor, + BasicValueConverter converter) { + super( baseDescriptor, arrayJdbcType, arrayTypeDescriptor ); + this.converter = converter; + this.jdbcValueBinder = (ValueBinder) arrayJdbcType.getBinder( converter.getRelationalJavaType() ); + this.jdbcValueExtractor = (ValueExtractor) arrayJdbcType.getExtractor( converter.getRelationalJavaType() ); + this.jdbcLiteralFormatter = (JdbcLiteralFormatter) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() ); + } + + @Override + public BasicValueConverter getValueConverter() { + return converter; + } + + @Override + public JavaType getJdbcJavaType() { + return converter.getRelationalJavaType(); + } + + @Override + public ValueExtractor getJdbcValueExtractor() { + return jdbcValueExtractor; + } + + @Override + public ValueBinder getJdbcValueBinder() { + return jdbcValueBinder; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter() { + return jdbcLiteralFormatter; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index 06909df6ea..37d369e159 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -199,7 +199,7 @@ public class EnumType> } } - private JavaType resolveRelationalJavaType( + private JavaType resolveRelationalJavaType( LocalJdbcTypeIndicators indicators, EnumJavaType enumJavaType) { return enumJavaType.getRecommendedJdbcType( indicators ) @@ -264,19 +264,15 @@ public class EnumType> final int type = Integer.decode( (String) parameters.get( TYPE ) ); return getConverterForType( enumJavaType, localIndicators, type ); } - + final JavaType relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType ); // the fallback return new OrdinalEnumValueConverter<>( enumJavaType, - getIntegerType().getRecommendedJdbcType( localIndicators ), - getIntegerType() + relationalJavaType.getRecommendedJdbcType( localIndicators ), + relationalJavaType ); } - private JavaType getIntegerType() { - return typeConfiguration.getJavaTypeRegistry().getDescriptor(Integer.class); - } - private JavaType getStringType() { return typeConfiguration.getJavaTypeRegistry().getDescriptor(String.class); } @@ -293,10 +289,11 @@ public class EnumType> ); } else { + final JavaType relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType ); return new OrdinalEnumValueConverter<>( enumJavaType, - getIntegerType().getRecommendedJdbcType( localIndicators ), - getIntegerType() + relationalJavaType.getRecommendedJdbcType( localIndicators ), + relationalJavaType ); } } @@ -306,10 +303,11 @@ public class EnumType> LocalJdbcTypeIndicators localIndicators, int type) { if ( isNumericType(type) ) { + final JavaType relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType ); return new OrdinalEnumValueConverter<>( enumJavaType, - getIntegerType().getRecommendedJdbcType( localIndicators ), - getIntegerType() + relationalJavaType.getRecommendedJdbcType( localIndicators ), + relationalJavaType ); } else if ( isCharacterType(type) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java new file mode 100644 index 0000000000..19c7174e63 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/ArrayConverter.java @@ -0,0 +1,84 @@ +/* + * 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.converter.internal; + +import java.lang.reflect.Array; + +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; + +/** + * Handles conversion to/from an array of a converted element type. + */ +public class ArrayConverter implements BasicValueConverter { + + private final BasicValueConverter elementConverter; + private final JavaType domainJavaType; + private final JavaType relationalJavaType; + + public ArrayConverter( + BasicValueConverter elementConverter, + JavaType domainJavaType, + JavaType relationalJavaType) { + this.elementConverter = elementConverter; + this.domainJavaType = domainJavaType; + this.relationalJavaType = relationalJavaType; + } + + @Override + public X toDomainValue(Y relationalForm) { + if ( relationalForm == null ) { + return null; + } + if ( relationalForm.getClass().getComponentType() == elementConverter.getDomainJavaType().getJavaTypeClass() ) { + //noinspection unchecked + return (X) relationalForm; + } + final Object[] relationalArray = (Object[]) relationalForm; + final Object[] domainArray = (Object[]) Array.newInstance( + elementConverter.getDomainJavaType().getJavaTypeClass(), + relationalArray.length + ); + for ( int i = 0; i < relationalArray.length; i++ ) { + domainArray[i] = elementConverter.toDomainValue( relationalArray[i] ); + } + //noinspection unchecked + return (X) domainArray; + } + + @Override + public Y toRelationalValue(X domainForm) { + if ( domainForm == null ) { + return null; + } + if ( domainForm.getClass().getComponentType() == elementConverter.getRelationalJavaType().getJavaTypeClass() ) { + //noinspection unchecked + return (Y) domainForm; + } + final Object[] domainArray = (Object[]) domainForm; + final Object[] relationalArray = (Object[]) Array.newInstance( + elementConverter.getRelationalJavaType().getJavaTypeClass(), + domainArray.length + ); + for ( int i = 0; i < domainArray.length; i++ ) { + relationalArray[i] = elementConverter.toRelationalValue( domainArray[i] ); + } + //noinspection unchecked + return (Y) relationalArray; + } + + @Override + public JavaType getDomainJavaType() { + return domainJavaType; + } + + @Override + public JavaType getRelationalJavaType() { + return relationalJavaType; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/CollectionConverter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/CollectionConverter.java new file mode 100644 index 0000000000..51d66c7c66 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/CollectionConverter.java @@ -0,0 +1,74 @@ +/* + * 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.converter.internal; + +import java.lang.reflect.Array; +import java.util.Collection; + +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType; + +/** + * Handles conversion to/from a collection of a converted element type. + */ +public class CollectionConverter, Y> implements BasicValueConverter { + + private final BasicValueConverter elementConverter; + private final BasicCollectionJavaType domainJavaType; + private final JavaType relationalJavaType; + + public CollectionConverter( + BasicValueConverter elementConverter, + BasicCollectionJavaType domainJavaType, + JavaType relationalJavaType) { + this.elementConverter = elementConverter; + this.domainJavaType = domainJavaType; + this.relationalJavaType = relationalJavaType; + } + + @Override + public X toDomainValue(Y relationalForm) { + if ( relationalForm == null ) { + return null; + } + final Object[] relationalArray = (Object[]) relationalForm; + final X domainForm = domainJavaType.getSemantics().instantiateRaw( relationalArray.length, null ); + for ( int i = 0; i < relationalArray.length; i++ ) { + domainForm.add( elementConverter.toDomainValue( relationalArray[i] ) ); + } + return domainForm; + } + + @Override + public Y toRelationalValue(X domainForm) { + if ( domainForm == null ) { + return null; + } + final Object[] relationalArray = (Object[]) Array.newInstance( + elementConverter.getRelationalJavaType().getJavaTypeClass(), + domainForm.size() + ); + int i = 0; + for ( Object domainValue : domainForm ) { + relationalArray[i++] = elementConverter.toRelationalValue( domainValue ); + } + //noinspection unchecked + return (Y) relationalArray; + } + + @Override + public JavaType getDomainJavaType() { + return domainJavaType; + } + + @Override + public JavaType getRelationalJavaType() { + return relationalJavaType; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/OrdinalEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/OrdinalEnumValueConverter.java index 96f3fb4c39..6737f67523 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/OrdinalEnumValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/internal/OrdinalEnumValueConverter.java @@ -40,9 +40,9 @@ public class OrdinalEnumValueConverter, N extends Number> impl return enumJavaType.fromOrdinal( relationalForm == null ? null : relationalForm.intValue() ); } - @Override @SuppressWarnings("unchecked") + @Override public N toRelationalValue(E domainForm) { - return (N) enumJavaType.toOrdinal( domainForm ); + return relationalJavaType.wrap( enumJavaType.toOrdinal( domainForm ), null ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java index 9b6ef16bc6..a01c7351de 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractArrayJavaType.java @@ -6,14 +6,17 @@ */ package org.hibernate.type.descriptor.java; +import java.lang.reflect.Array; import java.sql.Types; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.descriptor.converter.internal.ArrayConverter; import org.hibernate.type.BasicArrayType; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; +import org.hibernate.type.ConvertedBasicArrayType; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; @@ -66,21 +69,46 @@ public abstract class AbstractArrayJavaType extends AbstractClassJavaType< if ( elementType instanceof BasicPluralType || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) { return null; } - return typeConfiguration.standardBasicTypeForJavaType( - getJavaType(), - javaType -> { - JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); - if ( arrayJdbcType instanceof ArrayJdbcType ) { - arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( - typeConfiguration, - dialect, - elementType, - columnTypeInformation - ); + final BasicValueConverter valueConverter = elementType.getValueConverter(); + if ( valueConverter == null ) { + return typeConfiguration.standardBasicTypeForJavaType( + getJavaType(), + javaType -> { + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + //noinspection unchecked,rawtypes + return new BasicArrayType( elementType, arrayJdbcType, javaType ); } - //noinspection unchecked,rawtypes - return new BasicArrayType( elementType, arrayJdbcType, javaType ); - } - ); + ); + } + else { + final JavaType relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor( + Array.newInstance( valueConverter.getRelationalJavaType().getJavaTypeClass(), 0 ).getClass() + ); + + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + //noinspection unchecked,rawtypes + return new ConvertedBasicArrayType( + elementType, + arrayJdbcType, + this, + new ArrayConverter( valueConverter, this, relationalJavaType ) + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java index a8bc64a62d..f388a5d7ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ArrayJavaType.java @@ -18,10 +18,13 @@ import org.hibernate.engine.jdbc.BinaryStream; import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.descriptor.converter.internal.ArrayConverter; import org.hibernate.type.BasicArrayType; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; +import org.hibernate.type.ConvertedBasicArrayType; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; @@ -65,21 +68,47 @@ public class ArrayJavaType extends AbstractArrayJavaType { // Register the array type as that will be resolved in the next step typeConfiguration.getJavaTypeRegistry().addDescriptor( arrayJavaType ); } - return typeConfiguration.standardBasicTypeForJavaType( - arrayJavaType.getJavaType(), - javaType -> { - JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); - if ( arrayJdbcType instanceof ArrayJdbcType ) { - arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( - typeConfiguration, - dialect, - elementType, - columnTypeInformation - ); + //noinspection unchecked + final BasicValueConverter valueConverter = (BasicValueConverter) elementType.getValueConverter(); + if ( valueConverter == null ) { + return typeConfiguration.standardBasicTypeForJavaType( + arrayJavaType.getJavaType(), + javaType -> { + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + return new BasicArrayType<>( elementType, arrayJdbcType, javaType ); } - return new BasicArrayType<>( elementType, arrayJdbcType, javaType ); - } - ); + ); + } + else { + final JavaType relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor( + Array.newInstance( valueConverter.getRelationalJavaType().getJavaTypeClass(), 0 ).getClass() + ); + + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + //noinspection unchecked + return new ConvertedBasicArrayType<>( + elementType, + arrayJdbcType, + arrayJavaType, + new ArrayConverter<>( valueConverter, arrayJavaType, relationalJavaType ) + ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java index 57b057bc13..7c4c252a98 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayJavaType.java @@ -15,6 +15,7 @@ import java.util.Arrays; import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.BinaryStream; import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; +import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.descriptor.WrapperOptions; /** @@ -24,6 +25,7 @@ import org.hibernate.type.descriptor.WrapperOptions; */ public class ByteArrayJavaType extends AbstractClassJavaType { public static final ByteArrayJavaType INSTANCE = new ByteArrayJavaType(); + private static final Byte[] EMPTY_BYTE_ARRAY = new Byte[0]; @SuppressWarnings("unchecked") public ByteArrayJavaType() { @@ -117,6 +119,30 @@ public class ByteArrayJavaType extends AbstractClassJavaType { throw new HibernateException( "Unable to access lob stream", e ); } } + if ( value instanceof java.sql.Array ) { + try { + //noinspection unchecked + value = (X) ( (java.sql.Array) value ).getArray(); + if ( value instanceof Byte[] ) { + return (Byte[]) value; + } + else if ( value instanceof Object[] ) { + final Object[] array = (Object[]) value; + if ( array.length == 0 ) { + return EMPTY_BYTE_ARRAY; + } + final Byte[] bytes = new Byte[array.length]; + for ( int i = 0; i < array.length; i++ ) { + bytes[i] = ByteJavaType.INSTANCE.wrap( array[i], options ); + } + return bytes; + } + } + catch ( SQLException ex ) { + // This basically shouldn't happen unless you've lost connection to the database. + throw new HibernateException( ex ); + } + } throw unknownWrap( value.getClass() ); } @@ -125,21 +151,16 @@ public class ByteArrayJavaType extends AbstractClassJavaType { if ( bytes == null ) { return null; } - final Byte[] result = new Byte[bytes.length]; - for ( int i = 0; i < bytes.length; i++ ) { - result[i] = bytes[i]; - } - return result; + // Since a Byte[] can contain nulls but a byte[] can't, we have to serialize/deserialize the content + return (Byte[]) SerializationHelper.deserialize( bytes ); } private byte[] unwrapBytes(Byte[] bytes) { if ( bytes == null ) { return null; } - final byte[] result = new byte[bytes.length]; - for ( int i = 0; i < bytes.length; i++ ) { - result[i] = bytes[i]; - } - return result; + + // Since a Byte[] can contain nulls but a byte[] can't, we have to serialize/deserialize the content + return SerializationHelper.serialize( bytes ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/BasicCollectionJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/BasicCollectionJavaType.java index 27c0853bd5..1e3f705b8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/BasicCollectionJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/BasicCollectionJavaType.java @@ -22,7 +22,6 @@ import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.BinaryStream; import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.CollectionClassification; @@ -30,7 +29,10 @@ import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.type.BasicCollectionType; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.converter.internal.CollectionConverter; +import org.hibernate.type.ConvertedBasicCollectionType; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.AbstractJavaType; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; @@ -110,22 +112,47 @@ public class BasicCollectionJavaType, E> extends Abstrac // Register the collection type as that will be resolved in the next step typeConfiguration.getJavaTypeRegistry().addDescriptor( collectionJavaType ); } - return typeConfiguration.standardBasicTypeForJavaType( - collectionJavaType.getJavaType(), - javaType -> { - JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); - if ( arrayJdbcType instanceof ArrayJdbcType ) { - arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( - typeConfiguration, - dialect, - elementType, - columnTypeInformation - ); + final BasicValueConverter valueConverter = elementType.getValueConverter(); + if ( valueConverter == null ) { + return typeConfiguration.standardBasicTypeForJavaType( + collectionJavaType.getJavaType(), + javaType -> { + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + //noinspection unchecked,rawtypes + return new BasicCollectionType( elementType, arrayJdbcType, collectionJavaType ); } - //noinspection unchecked,rawtypes - return new BasicCollectionType( elementType, arrayJdbcType, collectionJavaType ); - } - ); + ); + } + else { + final JavaType relationalJavaType = typeConfiguration.getJavaTypeRegistry().resolveDescriptor( + Array.newInstance( valueConverter.getRelationalJavaType().getJavaTypeClass(), 0 ).getClass() + ); + + JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY ); + if ( arrayJdbcType instanceof ArrayJdbcType ) { + arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType( + typeConfiguration, + dialect, + elementType, + columnTypeInformation + ); + } + //noinspection unchecked,rawtypes + return new ConvertedBasicCollectionType<>( + elementType, + arrayJdbcType, + collectionJavaType, + new CollectionConverter( valueConverter, collectionJavaType, relationalJavaType ) + ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java index 9108a18cfb..702c56845f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java @@ -24,6 +24,8 @@ import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; +import org.hibernate.type.descriptor.java.ByteArrayJavaType; +import org.hibernate.type.descriptor.java.ByteJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterArray; import org.hibernate.type.spi.TypeConfiguration; @@ -86,9 +88,17 @@ public class ArrayJdbcType implements JdbcType { @Override public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaTypeDescriptor) { - //noinspection unchecked - final BasicPluralJavaType basicPluralJavaType = (BasicPluralJavaType) javaTypeDescriptor; - final JdbcLiteralFormatter elementFormatter = elementJdbcType.getJdbcLiteralFormatter( basicPluralJavaType.getElementJavaType() ); + final JavaType elementJavaType; + if ( javaTypeDescriptor instanceof ByteArrayJavaType ) { + // Special handling needed for Byte[], because that would conflict with the VARBINARY mapping + //noinspection unchecked + elementJavaType = (JavaType) ByteJavaType.INSTANCE; + } + else { + //noinspection unchecked + elementJavaType = ( (BasicPluralJavaType) javaTypeDescriptor ).getElementJavaType(); + } + final JdbcLiteralFormatter elementFormatter = elementJdbcType.getJdbcLiteralFormatter( elementJavaType ); return new JdbcLiteralFormatterArray<>( javaTypeDescriptor, elementFormatter ); } @@ -99,20 +109,18 @@ public class ArrayJdbcType implements JdbcType { @Override public ValueBinder getBinder(final JavaType javaTypeDescriptor) { - //noinspection unchecked - final BasicPluralJavaType containerJavaType = (BasicPluralJavaType) javaTypeDescriptor; return new BasicBinder( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - final java.sql.Array arr = getArray( value, containerJavaType, options ); + final java.sql.Array arr = getArray( value, options ); st.setArray( index, arr ); } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { - final java.sql.Array arr = getArray( value, containerJavaType, options ); + final java.sql.Array arr = getArray( value, options ); try { st.setObject( name, arr, java.sql.Types.ARRAY ); } @@ -123,9 +131,9 @@ public class ArrayJdbcType implements JdbcType { private java.sql.Array getArray( X value, - BasicPluralJavaType containerJavaType, WrapperOptions options) throws SQLException { final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration(); + final JdbcType elementJdbcType = ( (ArrayJdbcType) getJdbcType() ).getElementJdbcType(); final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry() .getDescriptor( elementJdbcType.getDefaultSqlTypeCode() ); final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options ); @@ -145,15 +153,31 @@ public class ArrayJdbcType implements JdbcType { elementJdbcJavaTypeClass, 0 ).getClass(); - final Object[] objects = javaTypeDescriptor.unwrap( value, arrayClass, options ); + final Object[] objects = getJavaType().unwrap( value, arrayClass, options ); final SharedSessionContractImplementor session = options.getSession(); // TODO: ideally, we would have the actual size or the actual type/column accessible // this is something that we would need for supporting composite types anyway + final JavaType elementJavaType; + if ( getJavaType() instanceof ByteArrayJavaType ) { + // Special handling needed for Byte[], because that would conflict with the VARBINARY mapping + //noinspection unchecked + elementJavaType = (JavaType) ByteJavaType.INSTANCE; + } + else { + //noinspection unchecked + elementJavaType = ( (BasicPluralJavaType) getJavaType() ).getElementJavaType(); + } final Size size = session.getJdbcServices() .getDialect() .getSizeStrategy() - .resolveSize( elementJdbcType, containerJavaType.getElementJavaType(), null, null, null ); + .resolveSize( + elementJdbcType, + elementJavaType, + null, + null, + null + ); String typeName = session.getTypeConfiguration() .getDdlTypeRegistry() .getDescriptor( elementJdbcType.getDdlTypeCode() ) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/TinyIntAsSmallIntJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/TinyIntAsSmallIntJdbcType.java new file mode 100644 index 0000000000..f1f3c8b824 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/TinyIntAsSmallIntJdbcType.java @@ -0,0 +1,54 @@ +/* + * 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 . + */ +package org.hibernate.type.descriptor.jdbc; + +import java.sql.Types; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Descriptor for {@link Types#TINYINT TINYINT} handling, when the {@link Types#SMALLINT SMALLINT} DDL type code is used. + * This type extends {@link SmallIntJdbcType}, because on the JDBC side we must bind {@link Short} instead of {@link Byte}, + * because it is not specified whether the conversion from {@link Byte} to {@link Short} is signed or unsigned, + * and we need the conversion to be signed, which is properly handled by the {@link org.hibernate.type.descriptor.java.JavaType#unwrap(Object, Class, WrapperOptions)} implementations. + */ +public class TinyIntAsSmallIntJdbcType extends SmallIntJdbcType { + public static final TinyIntAsSmallIntJdbcType INSTANCE = new TinyIntAsSmallIntJdbcType(); + + public TinyIntAsSmallIntJdbcType() { + } + + @Override + public int getJdbcTypeCode() { + return Types.TINYINT; + } + + @Override + public int getDdlTypeCode() { + return Types.SMALLINT; + } + + @Override + public String getFriendlyName() { + return "TINYINT"; + } + + @Override + public String toString() { + return "TinyIntAsSmallIntTypeDescriptor"; + } + + @Override + public JavaType getJdbcRecommendedJavaTypeMapping( + Integer length, + Integer scale, + TypeConfiguration typeConfiguration) { + return typeConfiguration.getJavaTypeRegistry().getDescriptor( Byte.class ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/EnumResolutionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/EnumResolutionTests.java index 2003a8a82b..ab2104ba49 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/EnumResolutionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/EnumResolutionTests.java @@ -59,7 +59,7 @@ public class EnumResolutionTests { verifyEnumResolution( entityBinding.getProperty( "rawEnum" ), Types.TINYINT, - Integer.class, + Byte.class, OrdinalEnumValueConverter.class, true ); @@ -74,7 +74,7 @@ public class EnumResolutionTests { verifyEnumResolution( entityBinding.getProperty( "unspecifiedMappingEnum" ), Types.TINYINT, - Integer.class, + Byte.class, OrdinalEnumValueConverter.class, true ); @@ -89,7 +89,7 @@ public class EnumResolutionTests { verifyEnumResolution( entityBinding.getProperty( "ordinalEnum" ), Types.TINYINT, - Integer.class, + Byte.class, OrdinalEnumValueConverter.class, true ); @@ -139,7 +139,7 @@ public class EnumResolutionTests { verifyEnumResolution( entityBinding.getProperty( "explicitEnum" ), Types.SMALLINT, - Integer.class, + Short.class, OrdinalEnumValueConverter.class, true ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/SmokeTests.java index 3d02d0e70a..b86efb920c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/SmokeTests.java @@ -8,17 +8,6 @@ package org.hibernate.orm.test.mapping; import java.sql.Statement; import java.sql.Types; -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Embeddable; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ModelPart; @@ -42,12 +31,25 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isOneOf; /** * @author Steve Ebersole @@ -98,7 +100,10 @@ public class SmokeTests { valueConverter.getDomainJavaType().getJavaTypeClass(), equalTo( genderAttrMapping.getJavaType().getJavaTypeClass() ) ); - assertThat( valueConverter.getRelationalJavaType().getJavaTypeClass(), equalTo( Integer.class ) ); + assertThat( + valueConverter.getRelationalJavaType().getJavaTypeClass(), + isOneOf( Byte.class, Short.class, Integer.class ) + ); assertThat( jdbcTypeRegistry.getDescriptor( valueConverter.getJdbcTypeCode() ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java index 7741ca32a0..916780fbbc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java @@ -38,12 +38,7 @@ import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.type.CustomType; -import org.hibernate.type.EnumType; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; -import org.hibernate.testing.hamcrest.AssignableMatcher; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; @@ -56,6 +51,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isOneOf; /** * @author Steve Ebersole @@ -170,7 +166,10 @@ public class SmokeTests { assertThat( selectedExpressible.getJdbcType().isInteger(), is( true ) ); final EnumValueConverter enumConverter = (EnumValueConverter) selectedExpressible.getValueConverter(); - assertThat( enumConverter.getRelationalJavaType().getJavaTypeClass(), AssignableMatcher.assignableTo( Integer.class ) ); + assertThat( + enumConverter.getRelationalJavaType().getJavaTypeClass(), + isOneOf( Byte.class, Short.class, Integer.class ) + ); assertThat( sqlAst.getDomainResultDescriptors().size(), is( 1 ) ); final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumArrayTest.java index 52a904251b..05166290bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumArrayTest.java @@ -14,8 +14,6 @@ import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.internal.util.SerializationHelper; -import org.hibernate.type.SqlTypes; -import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -41,7 +39,6 @@ import static org.junit.Assert.assertThat; * @author Christian Beikov */ @SkipForDialect(value = SybaseASEDialect.class, comment = "Sybase or the driver are trimming trailing zeros in byte arrays") -@SkipForDialect( value = OracleDialect.class, jiraKey = "HHH-16333", comment = "converters not handled properly" ) public class EnumArrayTest extends BaseNonConfigCoreFunctionalTestCase { @Override @@ -135,12 +132,13 @@ public class EnumArrayTest extends BaseNonConfigCoreFunctionalTestCase { } private Object nativeEnumArray(MyEnum... enums) { - final DdlTypeRegistry ddlTypeRegistry = sessionFactory().getTypeConfiguration().getDdlTypeRegistry(); - final String tinyintType = ddlTypeRegistry.getDescriptor( SqlTypes.TINYINT ).getRawTypeName(); - final String smallintType = ddlTypeRegistry.getDescriptor( SqlTypes.SMALLINT ).getRawTypeName(); - // We have to bind a Short[] if the DDL type is smallint to align with the Hibernate mapping, - // but also if the dialect supports arrays natively, because then a Short[] can be coerced to a Byte[] - if ( tinyintType.equals( smallintType ) || getDialect().supportsStandardArrays() ) { + // We also have to pass a Short[] for Oracle because that serializes to XML by default + if ( getDialect().supportsStandardArrays() || getDialect() instanceof OracleDialect ) { + // For native queries we must bind a Short[] instead of Byte[] even if we can use the "tinyint array" DDL type. + // This is because the JavaType we have registered for Byte[] does not implement BasicPluralJavaType. + // We can't make it implement that though, because that would be backwards incompatible, + // leading to Byte[] uses in the domain being treated as "tinyint array" or "smallint array" instead of varbinary. + // Luckily, JDBC drivers that support standard arrays are capable to coerce a Short[] to Byte[] final Short[] array = new Short[enums.length]; for ( int i = 0; i < enums.length; i++ ) { array[i] = enums[i] == null ? null : (short) enums[i].ordinal(); diff --git a/migration-guide.adoc b/migration-guide.adoc index 8165996578..0e2dfeb2e3 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -14,6 +14,23 @@ earlier versions, see any other pertinent migration guides as well. * link:{docsBase}/6.1/migration-guide/migration-guide.html[6.1 Migration guide] * link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide] +[[data-format-changes]] +== Data format changes + +[[data-format-wrapper-byte-array]] +=== Byte[] data format changes + +Prior to Hibernate 6.2, a `Byte[]` in the domain model was always just blindly converted to a `byte[]`, +possibly running into a `NullPointerException` if the `Byte[]` contains a `null` array element. + +To fix this, a `Byte[]` is now serialized with Java serialization to a `byte[]`, which allows handling nulls better. +This change is necessary to properly support plural basic types where the element type maps to the `Byte` Java type, +i.e. an array or collection of `Enum`. + +Since the use of `Byte[]` in a domain model did not make sense so far because null elements were impossible, +it is expected that most domain models were already using `byte[]`, so the breaking change was considered to be ok. + +Domain models that really used a `Byte[]` but never stored a `null`, must be migrated to `byte[]` now. [[ddl-changes]] == DDL type changes