HHH-16333 Handle converters properly in BasicPluralType

This commit is contained in:
Christian Beikov 2023-03-17 11:52:02 +01:00
parent f68ea21891
commit c54e156c14
30 changed files with 690 additions and 294 deletions

View File

@ -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 );

View File

@ -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 );

View File

@ -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<E extends Enum<E>,R> implements BasicValu
if ( style == EnumType.ORDINAL ) {
jdbcType = jdbcTypeRegistry.getDescriptor( enumJavaType.hasManyValues() ? SMALLINT : TINYINT );
final JavaType<Integer> jdbcJavaType = javaTypeRegistry.getDescriptor( Integer.class );
final JavaType<Integer> 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<String> jdbcJavaType = jdbcType.getJdbcRecommendedJavaTypeMapping(
jdbcTypeIndicators.getColumnPrecision(),
jdbcTypeIndicators.getColumnScale(),
typeConfiguration
);
converter = new NamedEnumValueConverter<>( enumJavaType, jdbcType, jdbcJavaType );
}
else {

View File

@ -374,8 +374,8 @@ public class InferredBasicValueResolver {
BasicJavaType<N> explicitJavaType,
JdbcType explicitJdbcType,
MetadataBuildingContext context) {
final JavaType<N> relationalJavaType = ordinalJavaType( explicitJavaType, context );
final JdbcType jdbcType = ordinalJdbcType( explicitJdbcType, enumJavaType, context );
final JavaType<N> relationalJavaType = ordinalJavaType( explicitJavaType, jdbcType, context );
return new EnumeratedValueResolution<>(
jdbcType,
@ -395,6 +395,7 @@ public class InferredBasicValueResolver {
private static <N extends Number> JavaType<N> ordinalJavaType(
JavaType<N> 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()
);
}
}

View File

@ -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 );

View File

@ -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 );

View File

@ -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 );

View File

@ -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;

View File

@ -4948,10 +4948,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> 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<T extends Statement> 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(

View File

@ -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 )
);
}
}

View File

@ -87,7 +87,7 @@ public abstract class AbstractStandardBasicType<T>
}
@Override
public JdbcLiteralFormatter getJdbcLiteralFormatter() {
public JdbcLiteralFormatter<T> getJdbcLiteralFormatter() {
return jdbcLiteralFormatter;
}

View File

@ -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<T>
private final BasicType<T> baseDescriptor;
private final String name;
private final ValueBinder<T[]> jdbcValueBinder;
private final ValueExtractor<T[]> jdbcValueExtractor;
private final JdbcLiteralFormatter<T[]> jdbcLiteralFormatter;
public BasicArrayType(BasicType<T> baseDescriptor, JdbcType arrayJdbcType, JavaType<T[]> arrayTypeDescriptor) {
super( arrayJdbcType, arrayTypeDescriptor );
this.baseDescriptor = baseDescriptor;
this.name = baseDescriptor.getName() + "[]";
final ValueBinder<T[]> jdbcValueBinder = super.getJdbcValueBinder();
final ValueExtractor<T[]> jdbcValueExtractor = super.getJdbcValueExtractor();
final JdbcLiteralFormatter jdbcLiteralFormatter = super.getJdbcLiteralFormatter();
//noinspection unchecked
final BasicValueConverter<T, Object> valueConverter = (BasicValueConverter<T, Object>) 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<T, Object> 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<T[]>() {
@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<T, Object> 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<T>
return true;
}
@Override
public ValueExtractor<T[]> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<T[]> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public JdbcLiteralFormatter getJdbcLiteralFormatter() {
return jdbcLiteralFormatter;
}
@Override
public <X> BasicType<X> resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<X> domainJtd) {
// TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively?

View File

@ -25,7 +25,10 @@ public class BasicCollectionType<C extends Collection<E>, E>
private final BasicType<E> baseDescriptor;
private final String name;
public BasicCollectionType(BasicType<E> baseDescriptor, JdbcType arrayJdbcType, BasicCollectionJavaType<C, E> collectionTypeDescriptor) {
public BasicCollectionType(
BasicType<E> baseDescriptor,
JdbcType arrayJdbcType,
BasicCollectionJavaType<C, E> collectionTypeDescriptor) {
super( arrayJdbcType, collectionTypeDescriptor );
this.baseDescriptor = baseDescriptor;
this.name = determineName( collectionTypeDescriptor, baseDescriptor );

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<T> extends BasicArrayType<T> {
private final BasicValueConverter<T[], ?> converter;
private final ValueExtractor<T[]> jdbcValueExtractor;
private final ValueBinder<T[]> jdbcValueBinder;
private final JdbcLiteralFormatter<T[]> jdbcLiteralFormatter;
@SuppressWarnings("unchecked")
public ConvertedBasicArrayType(
BasicType<T> baseDescriptor,
JdbcType arrayJdbcType,
JavaType<T[]> arrayTypeDescriptor,
BasicValueConverter<T[], ?> converter) {
super( baseDescriptor, arrayJdbcType, arrayTypeDescriptor );
this.converter = converter;
this.jdbcValueBinder = (ValueBinder<T[]>) arrayJdbcType.getBinder( converter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<T[]>) arrayJdbcType.getExtractor( converter.getRelationalJavaType() );
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<T[]>) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() );
}
@Override
public BasicValueConverter<T[], ?> getValueConverter() {
return converter;
}
@Override
public JavaType<?> getJdbcJavaType() {
return converter.getRelationalJavaType();
}
@Override
public ValueExtractor<T[]> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<T[]> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public JdbcLiteralFormatter<T[]> getJdbcLiteralFormatter() {
return jdbcLiteralFormatter;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<C extends Collection<E>, E> extends BasicCollectionType<C, E> {
private final BasicValueConverter<C, ?> converter;
private final ValueExtractor<C> jdbcValueExtractor;
private final ValueBinder<C> jdbcValueBinder;
private final JdbcLiteralFormatter<C> jdbcLiteralFormatter;
@SuppressWarnings("unchecked")
public ConvertedBasicCollectionType(
BasicType<E> baseDescriptor,
JdbcType arrayJdbcType,
BasicCollectionJavaType<C, E> arrayTypeDescriptor,
BasicValueConverter<C, ?> converter) {
super( baseDescriptor, arrayJdbcType, arrayTypeDescriptor );
this.converter = converter;
this.jdbcValueBinder = (ValueBinder<C>) arrayJdbcType.getBinder( converter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<C>) arrayJdbcType.getExtractor( converter.getRelationalJavaType() );
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<C>) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() );
}
@Override
public BasicValueConverter<C, ?> getValueConverter() {
return converter;
}
@Override
public JavaType<?> getJdbcJavaType() {
return converter.getRelationalJavaType();
}
@Override
public ValueExtractor<C> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<C> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public JdbcLiteralFormatter<C> getJdbcLiteralFormatter() {
return jdbcLiteralFormatter;
}
}

View File

@ -199,7 +199,7 @@ public class EnumType<T extends Enum<T>>
}
}
private JavaType<?> resolveRelationalJavaType(
private JavaType<? extends Number> resolveRelationalJavaType(
LocalJdbcTypeIndicators indicators,
EnumJavaType<?> enumJavaType) {
return enumJavaType.getRecommendedJdbcType( indicators )
@ -264,19 +264,15 @@ public class EnumType<T extends Enum<T>>
final int type = Integer.decode( (String) parameters.get( TYPE ) );
return getConverterForType( enumJavaType, localIndicators, type );
}
final JavaType<? extends Number> relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType );
// the fallback
return new OrdinalEnumValueConverter<>(
enumJavaType,
getIntegerType().getRecommendedJdbcType( localIndicators ),
getIntegerType()
relationalJavaType.getRecommendedJdbcType( localIndicators ),
relationalJavaType
);
}
private JavaType<Integer> getIntegerType() {
return typeConfiguration.getJavaTypeRegistry().getDescriptor(Integer.class);
}
private JavaType<String> getStringType() {
return typeConfiguration.getJavaTypeRegistry().getDescriptor(String.class);
}
@ -293,10 +289,11 @@ public class EnumType<T extends Enum<T>>
);
}
else {
final JavaType<? extends Number> relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType );
return new OrdinalEnumValueConverter<>(
enumJavaType,
getIntegerType().getRecommendedJdbcType( localIndicators ),
getIntegerType()
relationalJavaType.getRecommendedJdbcType( localIndicators ),
relationalJavaType
);
}
}
@ -306,10 +303,11 @@ public class EnumType<T extends Enum<T>>
LocalJdbcTypeIndicators localIndicators,
int type) {
if ( isNumericType(type) ) {
final JavaType<? extends Number> relationalJavaType = resolveRelationalJavaType( localIndicators, enumJavaType );
return new OrdinalEnumValueConverter<>(
enumJavaType,
getIntegerType().getRecommendedJdbcType( localIndicators ),
getIntegerType()
relationalJavaType.getRecommendedJdbcType( localIndicators ),
relationalJavaType
);
}
else if ( isCharacterType(type) ) {

View File

@ -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<X, Y> implements BasicValueConverter<X, Y> {
private final BasicValueConverter<Object, Object> elementConverter;
private final JavaType<X> domainJavaType;
private final JavaType<Y> relationalJavaType;
public ArrayConverter(
BasicValueConverter<Object, Object> elementConverter,
JavaType<X> domainJavaType,
JavaType<Y> 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<X> getDomainJavaType() {
return domainJavaType;
}
@Override
public JavaType<Y> getRelationalJavaType() {
return relationalJavaType;
}
}

View File

@ -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<X extends Collection<Object>, Y> implements BasicValueConverter<X, Y> {
private final BasicValueConverter<Object, Object> elementConverter;
private final BasicCollectionJavaType<X, ?> domainJavaType;
private final JavaType<Y> relationalJavaType;
public CollectionConverter(
BasicValueConverter<Object, Object> elementConverter,
BasicCollectionJavaType<X, ?> domainJavaType,
JavaType<Y> 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<X> getDomainJavaType() {
return domainJavaType;
}
@Override
public JavaType<Y> getRelationalJavaType() {
return relationalJavaType;
}
}

View File

@ -40,9 +40,9 @@ public class OrdinalEnumValueConverter<E extends Enum<E>, 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

View File

@ -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<T, E> 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<E, ?> 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<Object> 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 )
);
}
}
}

View File

@ -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<T> extends AbstractArrayJavaType<T[], T> {
// 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<Object, Object> valueConverter = (BasicValueConverter<Object, Object>) 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<Object> 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

View File

@ -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<Byte[]> {
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<Byte[]> {
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<Byte[]> {
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 );
}
}

View File

@ -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<C extends Collection<E>, 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<E, ?> 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<Object> 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

View File

@ -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 <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<T> basicPluralJavaType = (BasicPluralJavaType<T>) javaTypeDescriptor;
final JdbcLiteralFormatter<T> elementFormatter = elementJdbcType.getJdbcLiteralFormatter( basicPluralJavaType.getElementJavaType() );
final JavaType<T> elementJavaType;
if ( javaTypeDescriptor instanceof ByteArrayJavaType ) {
// Special handling needed for Byte[], because that would conflict with the VARBINARY mapping
//noinspection unchecked
elementJavaType = (JavaType<T>) ByteJavaType.INSTANCE;
}
else {
//noinspection unchecked
elementJavaType = ( (BasicPluralJavaType<T>) javaTypeDescriptor ).getElementJavaType();
}
final JdbcLiteralFormatter<T> elementFormatter = elementJdbcType.getJdbcLiteralFormatter( elementJavaType );
return new JdbcLiteralFormatterArray<>( javaTypeDescriptor, elementFormatter );
}
@ -99,20 +109,18 @@ public class ArrayJdbcType implements JdbcType {
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<X> containerJavaType = (BasicPluralJavaType<X>) javaTypeDescriptor;
return new BasicBinder<X>( 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<X> 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<X> elementJavaType;
if ( getJavaType() instanceof ByteArrayJavaType ) {
// Special handling needed for Byte[], because that would conflict with the VARBINARY mapping
//noinspection unchecked
elementJavaType = (JavaType<X>) ByteJavaType.INSTANCE;
}
else {
//noinspection unchecked
elementJavaType = ( (BasicPluralJavaType<X>) 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() )

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Byte.class );
}
}

View File

@ -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
);

View File

@ -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() ),

View File

@ -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 );

View File

@ -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();

View File

@ -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