HHH-16388 - Configuration setting for wrapper Byte[]/Character[] treatment

This commit is contained in:
Christian Beikov 2023-03-30 10:36:48 +02:00
parent f209423797
commit b5220ff929
15 changed files with 442 additions and 23 deletions

View File

@ -78,6 +78,7 @@ import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceException; import org.hibernate.service.spi.ServiceException;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.UserType; import org.hibernate.usertype.UserType;
@ -87,6 +88,8 @@ import jakarta.persistence.AttributeConverter;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
import jakarta.persistence.SharedCacheMode; import jakarta.persistence.SharedCacheMode;
import static org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -582,6 +585,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
private final MappingDefaultsImpl mappingDefaults; private final MappingDefaultsImpl mappingDefaults;
private final IdentifierGeneratorFactory identifierGeneratorFactory; private final IdentifierGeneratorFactory identifierGeneratorFactory;
private final TimeZoneStorageType defaultTimezoneStorage; private final TimeZoneStorageType defaultTimezoneStorage;
private final WrapperArrayHandling wrapperArrayHandling;
// todo (6.0) : remove bootstrapContext property along with the deprecated methods // todo (6.0) : remove bootstrapContext property along with the deprecated methods
private BootstrapContext bootstrapContext; private BootstrapContext bootstrapContext;
@ -619,6 +623,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
this.mappingDefaults = new MappingDefaultsImpl( serviceRegistry ); this.mappingDefaults = new MappingDefaultsImpl( serviceRegistry );
this.defaultTimezoneStorage = resolveTimeZoneStorageStrategy( configService ); this.defaultTimezoneStorage = resolveTimeZoneStorageStrategy( configService );
this.wrapperArrayHandling = resolveWrapperArrayHandling( configService );
this.multiTenancyEnabled = JdbcEnvironmentImpl.isMultiTenancyEnabled( serviceRegistry ); this.multiTenancyEnabled = JdbcEnvironmentImpl.isMultiTenancyEnabled( serviceRegistry );
this.xmlMappingEnabled = configService.getSetting( this.xmlMappingEnabled = configService.getSetting(
@ -868,6 +873,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
} }
} }
@Override
public WrapperArrayHandling getWrapperArrayHandling() {
return wrapperArrayHandling;
}
@Override @Override
public List<BasicTypeRegistration> getBasicTypeRegistrations() { public List<BasicTypeRegistration> getBasicTypeRegistrations() {
return basicTypeRegistrations; return basicTypeRegistrations;
@ -999,4 +1009,21 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
TimeZoneStorageType.DEFAULT TimeZoneStorageType.DEFAULT
); );
} }
private static WrapperArrayHandling resolveWrapperArrayHandling(
ConfigurationService configService) {
return configService.getSetting(
WRAPPER_ARRAY_HANDLING,
value -> {
if ( value == null ) {
throw new IllegalArgumentException( "Null value passed to convert" );
}
return value instanceof WrapperArrayHandling
? (WrapperArrayHandling) value
: WrapperArrayHandling.valueOf( value.toString().toUpperCase( Locale.ROOT ) );
},
WrapperArrayHandling.DISALLOW
);
}
} }

View File

@ -63,6 +63,10 @@ import org.hibernate.mapping.Table;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType;
@ -603,6 +607,21 @@ public class MetadataBuildingProcess {
} }
}; };
if ( options.getWrapperArrayHandling() == WrapperArrayHandling.LEGACY ) {
typeConfiguration.getJavaTypeRegistry().addDescriptor( ByteArrayJavaType.INSTANCE );
typeConfiguration.getJavaTypeRegistry().addDescriptor( CharacterArrayJavaType.INSTANCE );
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
basicTypeRegistry.addTypeReferenceRegistrationKey(
StandardBasicTypes.CHARACTER_ARRAY.getName(),
Character[].class.getName(), "Character[]"
);
basicTypeRegistry.addTypeReferenceRegistrationKey(
StandardBasicTypes.BINARY_WRAPPER.getName(),
Byte[].class.getName(), "Byte[]"
);
}
// add Dialect contributed types // add Dialect contributed types
final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect(); final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect();
dialect.contribute( typeContributions, options.getServiceRegistry() ); dialect.contribute( typeContributions, options.getServiceRegistry() );

View File

@ -19,6 +19,7 @@ import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.MetadataSourceType; import org.hibernate.cfg.MetadataSourceType;
import org.hibernate.dialect.TimeZoneSupport; import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.SharedCacheMode; import jakarta.persistence.SharedCacheMode;
@ -67,6 +68,11 @@ public abstract class AbstractDelegatingMetadataBuildingOptions implements Metad
return delegate.getTimeZoneSupport(); return delegate.getTimeZoneSupport();
} }
@Override
public WrapperArrayHandling getWrapperArrayHandling() {
return delegate.getWrapperArrayHandling();
}
@Override @Override
public List<BasicTypeRegistration> getBasicTypeRegistrations() { public List<BasicTypeRegistration> getBasicTypeRegistrations() {
return delegate.getBasicTypeRegistrations(); return delegate.getBasicTypeRegistrations();

View File

@ -23,6 +23,7 @@ import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard; import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard;
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver; import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.SharedCacheMode; import jakarta.persistence.SharedCacheMode;
@ -70,6 +71,13 @@ public interface MetadataBuildingOptions {
*/ */
TimeZoneSupport getTimeZoneSupport(); TimeZoneSupport getTimeZoneSupport();
/**
* @return the {@link WrapperArrayHandling} to use for wrapper arrays {@code Byte[]} and {@code Character[]}.
*
* @see org.hibernate.cfg.AvailableSettings#WRAPPER_ARRAY_HANDLING
*/
WrapperArrayHandling getWrapperArrayHandling();
default ManagedTypeRepresentationResolver getManagedTypeRepresentationResolver() { default ManagedTypeRepresentationResolver getManagedTypeRepresentationResolver() {
// for now always return the standard one // for now always return the standard one
return ManagedTypeRepresentationResolverStandard.INSTANCE; return ManagedTypeRepresentationResolverStandard.INSTANCE;

View File

@ -47,6 +47,7 @@ import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.CustomType; import org.hibernate.type.CustomType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.BasicPluralJavaType;
@ -857,6 +858,18 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return visitor.accept(this); return visitor.accept(this);
} }
@Internal
public boolean isDisallowedWrapperArray() {
return getBuildingContext().getBuildingOptions().getWrapperArrayHandling() == WrapperArrayHandling.DISALLOW
&& ( explicitJavaTypeAccess == null || explicitJavaTypeAccess.apply( getTypeConfiguration() ) == null )
&& isWrapperByteOrCharacterArray();
}
private boolean isWrapperByteOrCharacterArray() {
final Class<?> javaTypeClass = getResolution().getDomainJavaType().getJavaTypeClass();
return javaTypeClass == Byte[].class || javaTypeClass == Character[].class;
}
/** /**
* Resolved form of {@link BasicValue} as part of interpreting the * Resolved form of {@link BasicValue} as part of interpreting the
* boot-time model into the run-time model * boot-time model into the run-time model

View File

@ -18,6 +18,7 @@ import org.hibernate.Internal;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Database;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.Mapping;
@ -33,6 +34,7 @@ import org.hibernate.generator.Generator;
import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.WrapperArrayHandling;
/** /**
* A mapping model object representing a property or field of an {@linkplain PersistentClass entity} * A mapping model object representing a property or field of an {@linkplain PersistentClass entity}
@ -282,7 +284,23 @@ public class Property implements Serializable, MetaAttributable {
} }
public boolean isValid(Mapping mapping) throws MappingException { public boolean isValid(Mapping mapping) throws MappingException {
return getValue().isValid( mapping ); final Value value = getValue();
if ( value instanceof BasicValue && ( (BasicValue) value ).isDisallowedWrapperArray() ) {
throw new MappingException(
"The property " + persistentClass.getEntityName() + "#" + name +
" uses a wrapper type Byte[]/Character[] which indicates an issue in your domain model. " +
"These types have been treated like byte[]/char[] until Hibernate 6.2 which meant that " +
"null elements were not allowed, but on JDBC were processed like VARBINARY or VARCHAR. " +
"If you don't use nulls in your arrays, change the type of the property to byte[]/char[]. " +
"To allow explicit uses of the wrapper types Byte[]/Character[] which allows null element " +
"but has a different serialization format than before Hibernate 6.2, configure the " +
"setting " + AvailableSettings.WRAPPER_ARRAY_HANDLING + " to the value " + WrapperArrayHandling.ALLOW + ". " +
"To revert to the legacy treatment of these types, configure the value to " + WrapperArrayHandling.LEGACY + ". " +
"For more information on this matter, consult the migration guide of Hibernate 6.2 " +
"and the Javadoc of the org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING field."
);
}
return value.isValid( mapping );
} }
public String toString() { public String toString() {

View File

@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
@ -277,6 +278,17 @@ public class BasicTypeRegistry implements Serializable {
} }
} }
@Internal
public void addTypeReferenceRegistrationKey(String typeReferenceKey, String... additionalTypeReferenceKeys) {
final BasicTypeReference<?> basicTypeReference = typeReferencesByName.get( typeReferenceKey );
if ( basicTypeReference == null ) {
throw new IllegalArgumentException( "Couldn't find type reference with name: " + typeReferenceKey );
}
for ( String additionalTypeReferenceKey : additionalTypeReferenceKeys ) {
typeReferencesByName.put( additionalTypeReferenceKey, basicTypeReference );
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// priming // priming

View File

@ -14,6 +14,7 @@ import java.sql.Statement;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgresPlusDialect; import org.hibernate.dialect.PostgresPlusDialect;
@ -26,8 +27,10 @@ import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.RequiresDialectFeatureGroup; import org.hibernate.testing.orm.junit.RequiresDialectFeatureGroup;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -48,6 +51,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
annotatedClasses = { SomeEntity.class, SomeOtherEntity.class } annotatedClasses = { SomeEntity.class, SomeOtherEntity.class }
) )
@SessionFactory @SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class BasicOperationsTest { public class BasicOperationsTest {
private static final String SOME_ENTITY_TABLE_NAME = "SOMEENTITY"; private static final String SOME_ENTITY_TABLE_NAME = "SOMEENTITY";

View File

@ -9,8 +9,11 @@ package org.hibernate.orm.test.annotations.lob;
import java.util.Arrays; import java.util.Arrays;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.SybaseDialect;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
@ -28,6 +31,12 @@ import junit.framework.AssertionFailedError;
public class ImageTest extends BaseCoreFunctionalTestCase { public class ImageTest extends BaseCoreFunctionalTestCase {
private static final int ARRAY_SIZE = 10000; private static final int ARRAY_SIZE = 10000;
@Override
protected void configure(Configuration configuration) {
super.configure( configuration );
configuration.setProperty( AvailableSettings.WRAPPER_ARRAY_HANDLING, WrapperArrayHandling.ALLOW.name() );
}
@Test @Test
public void testBoundedLongByteArrayAccess() { public void testBoundedLongByteArrayAccess() {
byte[] original = buildRecursively(ARRAY_SIZE, true); byte[] original = buildRecursively(ARRAY_SIZE, true);

View File

@ -13,6 +13,7 @@ import jakarta.persistence.Lob;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType; import org.hibernate.annotations.JavaType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
@ -24,8 +25,10 @@ import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -40,6 +43,9 @@ import static org.hamcrest.Matchers.isOneOf;
*/ */
@DomainModel(annotatedClasses = ByteArrayMappingTests.EntityOfByteArrays.class) @DomainModel(annotatedClasses = ByteArrayMappingTests.EntityOfByteArrays.class)
@SessionFactory @SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class ByteArrayMappingTests { public class ByteArrayMappingTests {
@Test @Test

View File

@ -13,6 +13,7 @@ import jakarta.persistence.Lob;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType; import org.hibernate.annotations.JavaType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
@ -24,8 +25,10 @@ import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -39,6 +42,9 @@ import static org.hamcrest.Matchers.isOneOf;
*/ */
@DomainModel(annotatedClasses = CharacterArrayMappingTests.EntityWithCharArrays.class) @DomainModel(annotatedClasses = CharacterArrayMappingTests.EntityWithCharArrays.class)
@SessionFactory @SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class CharacterArrayMappingTests { public class CharacterArrayMappingTests {
@Test @Test
public void verifyMappings(SessionFactoryScope scope) { public void verifyMappings(SessionFactoryScope scope) {

View File

@ -13,6 +13,7 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.JavaType; import org.hibernate.annotations.JavaType;
import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.NationalizationSupport; import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
@ -27,8 +28,10 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -45,6 +48,9 @@ import static org.hamcrest.Matchers.isOneOf;
@DomainModel(annotatedClasses = CharacterArrayNationalizedMappingTests.EntityWithCharArrays.class) @DomainModel(annotatedClasses = CharacterArrayNationalizedMappingTests.EntityWithCharArrays.class)
@SessionFactory @SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsNationalizedData.class) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsNationalizedData.class)
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class CharacterArrayNationalizedMappingTests { public class CharacterArrayNationalizedMappingTests {
@Test @Test
public void verifyMappings(SessionFactoryScope scope) { public void verifyMappings(SessionFactoryScope scope) {

View File

@ -0,0 +1,55 @@
/*
* 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.orm.test.mapping.basic;
import org.hibernate.MappingException;
import org.hibernate.internal.util.ExceptionHelper;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
/**
* Tests for mapping wrapper values
*/
@DomainModel(annotatedClasses = WrapperArrayHandlingDisallowTests.EntityOfByteArrays.class)
@SessionFactory
public class WrapperArrayHandlingDisallowTests {
@Test
public void verifyByteArrayMappings(SessionFactoryScope scope) {
try {
scope.getSessionFactory();
Assertions.fail( "Should fail boot validation!" );
}
catch (Exception e) {
final Throwable rootCause = ExceptionHelper.getRootCause( e );
Assertions.assertEquals( MappingException.class, rootCause.getClass() );
assertThat( rootCause.getMessage(), containsString( WrapperArrayHandlingDisallowTests.EntityOfByteArrays.class.getName() + "#wrapper" ) );
}
}
@Entity(name = "EntityOfByteArrays")
@Table(name = "EntityOfByteArrays")
public static class EntityOfByteArrays {
@Id
public Integer id;
private Byte[] wrapper;
public EntityOfByteArrays() {
}
}
}

View File

@ -0,0 +1,250 @@
/*
* 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.orm.test.mapping.basic;
import java.sql.Types;
import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.NationalizationSupport;
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.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
* Tests for mapping wrapper values
*/
@DomainModel(annotatedClasses = {
WrapperArrayHandlingLegacyTests.EntityOfByteArrays.class,
WrapperArrayHandlingLegacyTests.EntityWithCharArrays.class,
})
@SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "LEGACY")
)
public class WrapperArrayHandlingLegacyTests {
@Test
public void verifyByteArrayMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( WrapperArrayHandlingLegacyTests.EntityOfByteArrays.class);
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitive");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(byte[].class));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.VARBINARY ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.VARBINARY ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitiveLob");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(byte[].class));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.BLOB ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperLob");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.BLOB ) ) );
}
scope.inTransaction(
(session) -> {
session.persist(
new EntityOfByteArrays( 1, "abc".getBytes(), new Byte[] { (byte) 1 })
);
}
);
scope.inTransaction(
(session) -> session.get( EntityOfByteArrays.class, 1)
);
}
@Test
public void verifyCharacterArrayMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final JdbcTypeRegistry jdbcRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( WrapperArrayHandlingLegacyTests.EntityWithCharArrays.class);
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitive");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcRegistry.getDescriptor( Types.VARCHAR)));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcRegistry.getDescriptor( Types.VARCHAR)));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitiveClob");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcRegistry.getDescriptor( Types.CLOB)));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperClob");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcRegistry.getDescriptor( Types.CLOB)));
}
}
@Test
public void verifyCharacterArrayNationalizedMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor(
WrapperArrayHandlingLegacyTests.EntityWithCharArrays.class);
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final NationalizationSupport nationalizationSupport = dialect.getNationalizationSupport();
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitiveNVarchar");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getVarcharVariantCode())));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperNVarchar");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getVarcharVariantCode())));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitiveNClob");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getClobVariantCode())));
}
{
final BasicAttributeMapping attributeMapping = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperNClob");
final JdbcMapping jdbcMapping = attributeMapping.getJdbcMapping();
assertThat( jdbcMapping.getJdbcType(), is( jdbcTypeRegistry.getDescriptor( nationalizationSupport.getClobVariantCode())));
}
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.createMutationQuery("delete EntityOfByteArrays").executeUpdate();
session.createMutationQuery("delete EntityWithCharArrays").executeUpdate();
}
);
}
@Entity(name = "EntityOfByteArrays")
@Table(name = "EntityOfByteArrays")
public static class EntityOfByteArrays {
@Id
public Integer id;
//tag::basic-bytearray-example[]
// mapped as VARBINARY
private byte[] primitive;
private Byte[] wrapper;
// mapped as (materialized) BLOB
@Lob
private byte[] primitiveLob;
@Lob
private Byte[] wrapperLob;
//end::basic-bytearray-example[]
public EntityOfByteArrays() {
}
public EntityOfByteArrays(Integer id, byte[] primitive, Byte[] wrapper) {
this.id = id;
this.primitive = primitive;
this.wrapper = wrapper;
this.primitiveLob = primitive;
this.wrapperLob = wrapper;
}
public EntityOfByteArrays(Integer id, byte[] primitive, Byte[] wrapper, byte[] primitiveLob, Byte[] wrapperLob) {
this.id = id;
this.primitive = primitive;
this.wrapper = wrapper;
this.primitiveLob = primitiveLob;
this.wrapperLob = wrapperLob;
}
}
@Entity(name = "EntityWithCharArrays")
@Table(name = "EntityWithCharArrays")
public static class EntityWithCharArrays {
@Id
public Integer id;
// mapped as VARCHAR
char[] primitive;
Character[] wrapper;
// mapped as CLOB
@Lob
char[] primitiveClob;
@Lob
Character[] wrapperClob;
// mapped as NVARCHAR
@Nationalized
char[] primitiveNVarchar;
@Nationalized
Character[] wrapperNVarchar;
// mapped as NCLOB
@Lob
@Nationalized
char[] primitiveNClob;
@Lob
@Nationalized
Character[] wrapperNClob;
}
}

View File

@ -14,25 +14,3 @@ 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.1/migration-guide/migration-guide.html[6.1 Migration guide]
* link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide] * link:{docsBase}/6.0/migration-guide/migration-guide.html[6.0 Migration guide]
[[ddl-changes]]
== DDL type changes
[[ddl-offset-time]]
=== OffsetTime mapping changes
`OffsetTime` now depends on `@TimeZoneStorage` and the `hibernate.timezone.default_storage` setting.
Since the default for this setting is now `TimeZoneStorageType.DEFAULT`, this means that the DDL expectations for such columns changed.
If the target database supports time zone types natively like H2, Oracle, SQL Server and DB2 z/OS,
the type code `SqlTypes.TIME_WITH_TIMEZONE` is now used, which maps to the DDL type `time with time zone`.
Due to this change, schema validation errors could occur on existing databases.
The migration to `time with time zone` requires a migration expression like `cast(old as time with time zone)`
which will interpret the previous time as local time and compute the offset for the `time with time zone` based on the current date
and time zone settings of your database session.
If the target database does not support time zone types natively, Hibernate behaves just like before.
To retain backwards compatibility, configure the setting `hibernate.timezone.default_storage` to `NORMALIZE`.