HHH-16333 Get rid of special Character[] and Byte[] handling

This commit is contained in:
Christian Beikov 2023-03-22 10:08:33 +01:00
parent c54e156c14
commit 4b1f56951b
20 changed files with 270 additions and 126 deletions

View File

@ -689,7 +689,11 @@ See <<basic-nationalized>> for details on mapping strings using nationalized cha
[[basic-chararray]]
==== Character arrays
By default, Hibernate maps `Character[]` and `char[]` to the `VARCHAR` JDBC type.
By default, Hibernate maps `char[]` to the `VARCHAR` JDBC type.
Since `Character[]` can contain null elements, it is mapped as <<basic-mapping-array,basic array type>> instead.
Prior to Hibernate 6.2, also `Character[]` mapped to `VARCHAR`, yet disallowed `null` elements.
To continue mapping `Character[]` to the `VARCHAR` JDBC type, or for LOBs mapping to the `CLOB` JDBC type,
it is necessary to annotate the persistent attribute with `@JavaType( CharacterArrayJavaType.class )`.
[[basic-string-example-implicit]]
.Mapping Character
@ -872,8 +876,11 @@ include::{example-dir-basic-mapping}/basic/NClobCharArrayTest.java[tags=basic-nc
[[basic-bytearray]]
==== Byte array
By default, Hibernate maps values of type `byte[]` and `Byte[]` to the JDBC type
`VARBINARY`.
By default, Hibernate maps `byte[]` to the `VARBINARY` JDBC type.
Since `Byte[]` can contain null elements, it is mapped as <<basic-mapping-array,basic array type>> instead.
Prior to Hibernate 6.2, also `Byte[]` mapped to `VARBINARY`, yet disallowed `null` elements.
To continue mapping `Byte[]` to the `VARBINARY` JDBC type, or for LOBs mapping to the `BLOB` JDBC type,
it is necessary to annotate the persistent attribute with `@JavaType( ByteArrayJavaType.class )`.
[[basic-bytearray-example]]
.Mapping arrays of bytes

View File

@ -12,11 +12,16 @@
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
@ -27,6 +32,9 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isOneOf;
/**
* @author Steve Ebersole
@ -40,6 +48,7 @@ public void verifyMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor(EntityOfByteArrays.class);
@ -54,6 +63,28 @@ public void verifyMappings(SessionFactoryScope scope) {
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
if ( dialect.supportsStandardArrays() ) {
assertThat( jdbcMapping.getJdbcType(), instanceOf( ArrayJdbcType.class ) );
assertThat(
( (ArrayJdbcType) jdbcMapping.getJdbcType() ).getElementJdbcType(),
is( jdbcTypeRegistry.getDescriptor( Types.TINYINT ) )
);
}
else {
assertThat(
jdbcMapping.getJdbcType(),
isOneOf(
jdbcTypeRegistry.getDescriptor( SqlTypes.ARRAY ),
jdbcTypeRegistry.getDescriptor( SqlTypes.SQLXML )
)
);
}
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperOld");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.VARBINARY ) ) );
}
@ -102,11 +133,14 @@ public static class EntityOfByteArrays {
// mapped as VARBINARY
private byte[] primitive;
private Byte[] wrapper;
@JavaType( ByteArrayJavaType.class )
private Byte[] wrapperOld;
// mapped as (materialized) BLOB
@Lob
private byte[] primitiveLob;
@Lob
@JavaType( ByteArrayJavaType.class )
private Byte[] wrapperLob;
//end::basic-bytearray-example[]

View File

@ -12,10 +12,15 @@
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
@ -25,6 +30,9 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isOneOf;
/**
* @author Steve Ebersole
@ -37,6 +45,7 @@ public void verifyMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final JdbcTypeRegistry jdbcRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor(EntityWithCharArrays.class);
@ -49,6 +58,27 @@ public void verifyMappings(SessionFactoryScope scope) {
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
if ( dialect.supportsStandardArrays() ) {
assertThat( jdbcMapping.getJdbcType(), instanceOf( ArrayJdbcType.class ) );
assertThat(
( (ArrayJdbcType) jdbcMapping.getJdbcType() ).getElementJdbcType(),
is( jdbcRegistry.getDescriptor( Types.CHAR ) )
);
}
else {
assertThat(
jdbcMapping.getJdbcType(),
isOneOf(
jdbcRegistry.getDescriptor( SqlTypes.ARRAY ),
jdbcRegistry.getDescriptor( SqlTypes.SQLXML )
)
);
}
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperOld");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcRegistry.getDescriptor( Types.VARCHAR)));
}
@ -76,11 +106,14 @@ public static class EntityWithCharArrays {
// mapped as VARCHAR
char[] primitive;
Character[] wrapper;
@JavaType( CharacterArrayJavaType.class )
Character[] wrapperOld;
// mapped as CLOB
@Lob
char[] primitiveClob;
@Lob
@JavaType( CharacterArrayJavaType.class )
Character[] wrapperClob;
//end::basic-chararray-example[]
}

View File

@ -11,6 +11,7 @@
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType;
import org.hibernate.annotations.Nationalized;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.NationalizationSupport;
@ -18,6 +19,9 @@
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
@ -28,7 +32,10 @@
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isOneOf;
/**
* @see CharacterArrayMappingTests
@ -59,6 +66,27 @@ public void verifyMappings(SessionFactoryScope scope) {
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperNVarchar");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
if ( dialect.supportsStandardArrays() ) {
assertThat( jdbcMapping.getJdbcType(), instanceOf( ArrayJdbcType.class ) );
assertThat(
( (ArrayJdbcType) jdbcMapping.getJdbcType() ).getElementJdbcType(),
is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getCharVariantCode() ) )
);
}
else {
assertThat(
jdbcMapping.getJdbcType(),
isOneOf(
jdbcTypeRegistry.getDescriptor( SqlTypes.ARRAY ),
jdbcTypeRegistry.getDescriptor( SqlTypes.SQLXML )
)
);
}
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperNVarcharOld");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getVarcharVariantCode())));
}
@ -88,6 +116,9 @@ public static class EntityWithCharArrays {
char[] primitiveNVarchar;
@Nationalized
Character[] wrapperNVarchar;
@Nationalized
@JavaType( CharacterArrayJavaType.class )
Character[] wrapperNVarcharOld;
// mapped as NCLOB
@Lob
@ -95,6 +126,7 @@ public static class EntityWithCharArrays {
char[] primitiveNClob;
@Lob
@Nationalized
@JavaType( CharacterArrayJavaType.class )
Character[] wrapperNClob;
//end::basic-nchararray-example[]
}

View File

@ -198,7 +198,7 @@ else if ( JavaTypeHelper.isTemporal( elementJtd ) ) {
registeredType = registeredElementType == null ? null : containerJtd.resolveType(
typeConfiguration,
dialect,
registeredElementType,
resolveSqlTypeIndicators( stdIndicators, registeredElementType, elementJtd ),
columnTypeInformation
);
if ( registeredType != null ) {

View File

@ -777,7 +777,7 @@ public static void prime(TypeConfiguration typeConfiguration) {
BINARY_WRAPPER,
"org.hibernate.type.WrapperBinaryType",
basicTypeRegistry,
"binary_wrapper", "wrapper-binary", "Byte[]", Byte[].class.getName()
"binary_wrapper", "wrapper-binary"//, "Byte[]", Byte[].class.getName()
);
handle(
@ -905,7 +905,7 @@ public static void prime(TypeConfiguration typeConfiguration) {
CHARACTER_ARRAY,
"org.hibernate.type.CharacterArrayType",
basicTypeRegistry,
"wrapper-characters", Character[].class.getName(), "Character[]"
"wrapper-characters"//, Character[].class.getName(), "Character[]"
);
handle(

View File

@ -8,6 +8,7 @@
import java.lang.reflect.Array;
import java.sql.Types;
import java.util.function.Function;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
@ -71,22 +72,23 @@ public BasicType<?> resolveType(
}
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 );
}
);
final Function<JavaType<T>, BasicType<T>> creator = 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 );
};
if ( typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType ) {
return typeConfiguration.standardBasicTypeForJavaType( getJavaType(), creator );
}
return creator.apply( this );
}
else {
final JavaType<Object> relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor(

View File

@ -10,6 +10,7 @@
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.sql.Types;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
@ -71,21 +72,22 @@ public BasicType<?> resolveType(
//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 );
}
);
final Function<JavaType<T[]>, BasicType<T[]>> creator = 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 );
};
if ( typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType ) {
return typeConfiguration.standardBasicTypeForJavaType( arrayJavaType.getJavaType(), creator );
}
return creator.apply( arrayJavaType );
}
else {
final JavaType<Object> relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor(

View File

@ -15,17 +15,20 @@
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.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
/**
* Descriptor for {@code Byte[]} handling.
* Descriptor for {@code Byte[]} handling, which disallows {@code null} elements.
* This {@link JavaType} is useful if the domain model uses {@code Byte[]} and wants to map to {@link SqlTypes#VARBINARY}.
*
* @author Steve Ebersole
*/
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() {
@ -45,6 +48,15 @@ public int extractHashCode(Byte[] bytes) {
return hashCode;
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
// match legacy behavior
final JdbcType descriptor = indicators.getJdbcType( indicators.resolveJdbcTypeCode( SqlTypes.VARBINARY ) );
return descriptor instanceof AdjustableJdbcType
? ( (AdjustableJdbcType) descriptor ).resolveIndicatedType( indicators, this )
: descriptor;
}
@Override
public String toString(Byte[] bytes) {
final StringBuilder buf = new StringBuilder();
@ -119,30 +131,6 @@ public <X> Byte[] wrap(X value, WrapperOptions options) {
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() );
}
@ -151,16 +139,21 @@ private Byte[] wrapBytes(byte[] bytes) {
if ( bytes == null ) {
return null;
}
// Since a Byte[] can contain nulls but a byte[] can't, we have to serialize/deserialize the content
return (Byte[]) SerializationHelper.deserialize( bytes );
final Byte[] result = new Byte[bytes.length];
for ( int i = 0; i < bytes.length; i++ ) {
result[i] = bytes[i];
}
return result;
}
private byte[] unwrapBytes(Byte[] bytes) {
if ( bytes == null ) {
return null;
}
// Since a Byte[] can contain nulls but a byte[] can't, we have to serialize/deserialize the content
return SerializationHelper.serialize( bytes );
final byte[] result = new byte[bytes.length];
for ( int i = 0; i < bytes.length; i++ ) {
result[i] = bytes[i];
}
return result;
}
}

View File

@ -13,10 +13,16 @@
import org.hibernate.engine.jdbc.CharacterStream;
import org.hibernate.engine.jdbc.internal.CharacterStreamImpl;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.JdbcTypeJavaClassMappings;
/**
* Descriptor for {@code Character[]} handling.
* Descriptor for {@code Character[]} handling, which disallows {@code null} elements.
* This {@link JavaType} is useful if the domain model uses {@code Character[]} and wants to map to {@link SqlTypes#VARCHAR}.
*
* @author Steve Ebersole
*/
@ -53,6 +59,15 @@ public int extractHashCode(Character[] chars) {
return hashCode;
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
// match legacy behavior
final JdbcType descriptor = indicators.getJdbcType( indicators.resolveJdbcTypeCode( SqlTypes.VARCHAR ) );
return descriptor instanceof AdjustableJdbcType
? ( (AdjustableJdbcType) descriptor ).resolveIndicatedType( indicators, this )
: descriptor;
}
@SuppressWarnings("unchecked")
@Override
public <X> X unwrap(Character[] value, Class<X> type, WrapperOptions options) {

View File

@ -14,6 +14,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
@ -114,22 +115,24 @@ public BasicType<?> resolveType(
}
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 );
}
);
final Function<JavaType<Object>, BasicType<Object>> creator = 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 );
};
if ( typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType ) {
return typeConfiguration.standardBasicTypeForJavaType( collectionJavaType.getJavaType(), creator );
}
//noinspection unchecked
return creator.apply( (JavaType<Object>) (JavaType<?>) collectionJavaType );
}
else {
final JavaType<Object> relationalJavaType = typeConfiguration.getJavaTypeRegistry().resolveDescriptor(

View File

@ -123,8 +123,8 @@ public static void prime(BaselineTarget target) {
target.addBaselineDescriptor( ClobJavaType.INSTANCE );
target.addBaselineDescriptor( NClobJavaType.INSTANCE );
target.addBaselineDescriptor( ByteArrayJavaType.INSTANCE );
target.addBaselineDescriptor( CharacterArrayJavaType.INSTANCE );
// target.addBaselineDescriptor( ByteArrayJavaType.INSTANCE );
// target.addBaselineDescriptor( CharacterArrayJavaType.INSTANCE );
target.addBaselineDescriptor( PrimitiveByteArrayJavaType.INSTANCE );
target.addBaselineDescriptor( PrimitiveCharacterArrayJavaType.INSTANCE );

View File

@ -147,8 +147,8 @@ private static ConcurrentHashMap<Class, Integer> buildJavaClassToJdbcTypeCodeMap
// additional "common sense" registrations
workMap.put( Character.class, SqlTypes.CHAR );
workMap.put( char[].class, SqlTypes.VARCHAR );
workMap.put( Character[].class, SqlTypes.VARCHAR );
workMap.put( Byte[].class, SqlTypes.VARBINARY );
// workMap.put( Character[].class, SqlTypes.VARCHAR );
// workMap.put( Byte[].class, SqlTypes.VARBINARY );
workMap.put( java.util.Date.class, SqlTypes.TIMESTAMP );
workMap.put( Calendar.class, SqlTypes.TIMESTAMP );

View File

@ -6,49 +6,72 @@
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
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.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},
* This type binds and extracts {@link Short} instead of {@link Byte} through JDBC,
* 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.
* yet 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 class TinyIntAsSmallIntJdbcType extends TinyIntJdbcType {
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";
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Short.class;
}
@Override
public String toString() {
return "TinyIntAsSmallIntTypeDescriptor";
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setShort( index, javaType.unwrap( value, Short.class, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setShort( name, javaType.unwrap( value, Short.class, options ) );
}
};
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer length,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().getDescriptor( Byte.class );
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaType.wrap( rs.getShort( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getShort( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaType.wrap( statement.getShort( name ), options );
}
};
}
}

View File

@ -5,6 +5,9 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations.lob;
import org.hibernate.annotations.JavaType;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import jakarta.persistence.Column;
import jakarta.persistence.Lob;
import jakarta.persistence.MappedSuperclass;
@ -40,6 +43,7 @@ public void setFullText(String fullText) {
@Lob
@Column(name = "fld_code")
@JavaType( CharacterArrayJavaType.class )
public Character[] getCode() {
return code;
}

View File

@ -5,6 +5,9 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations.lob;
import org.hibernate.annotations.JavaType;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import jakarta.persistence.Lob;
import jakarta.persistence.MappedSuperclass;
@ -26,6 +29,7 @@ public void setMetadata(byte[] metadata) {
}
@Lob
@JavaType( ByteArrayJavaType.class )
public Byte[] getHeader() {
return header;
}

View File

@ -11,7 +11,9 @@
import java.sql.Types;
import org.hibernate.annotations.JavaType;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@ -60,6 +62,7 @@ public void setName(char[] name) {
}
@JdbcTypeCode( Types.LONGVARCHAR )
@JavaType( CharacterArrayJavaType.class )
public Character[] getWhatEver() {
return whatEver;
}

View File

@ -13,7 +13,9 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType;
import org.hibernate.query.Query;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
@ -121,6 +123,7 @@ public void testMultipleUpdates(SessionFactoryScope scope) {
@Table(name = "DemoEntity")
public static class DemoEntity {
@Id
@JavaType( CharacterArrayJavaType.class )
public Character[] id;
public String name;
}

View File

@ -13,6 +13,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import org.hibernate.annotations.JavaType;
import org.hibernate.annotations.Nationalized;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
@ -32,6 +33,7 @@
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
@ -62,6 +64,7 @@ public static class NationalizedEntity {
private Character ncharacterAtt;
@Nationalized
@JavaType( CharacterArrayJavaType.class )
private Character[] ncharArrAtt;
@Lob
@ -126,7 +129,7 @@ public void simpleNationalizedTest() {
prop = pc.getProperty( "ncharArrAtt" );
type = (BasicType<?>) prop.getType();
assertSame( CharacterArrayJavaType.INSTANCE, type.getJavaTypeDescriptor() );
assertInstanceOf( CharacterArrayJavaType.class, type.getJavaTypeDescriptor() );
if ( dialect.getNationalizationSupport() != NationalizationSupport.EXPLICIT ) {
assertSame( jdbcTypeRegistry.getDescriptor( Types.VARCHAR ), type.getJdbcType() );
}

View File

@ -14,23 +14,6 @@ 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