HHH-16388 - Configuration setting for wrapper Byte[]/Character[] treatment
This commit is contained in:
parent
b799da7b60
commit
214b647f0f
|
@ -78,6 +78,7 @@ import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
|||
import org.hibernate.service.ServiceRegistry;
|
||||
import org.hibernate.service.spi.ServiceException;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
import org.hibernate.usertype.UserType;
|
||||
|
||||
|
@ -87,6 +88,8 @@ import jakarta.persistence.AttributeConverter;
|
|||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.SharedCacheMode;
|
||||
|
||||
import static org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -582,6 +585,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
private final MappingDefaultsImpl mappingDefaults;
|
||||
private final IdentifierGeneratorFactory identifierGeneratorFactory;
|
||||
private final TimeZoneStorageType defaultTimezoneStorage;
|
||||
private final WrapperArrayHandling wrapperArrayHandling;
|
||||
|
||||
// todo (6.0) : remove bootstrapContext property along with the deprecated methods
|
||||
private BootstrapContext bootstrapContext;
|
||||
|
@ -619,6 +623,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
this.mappingDefaults = new MappingDefaultsImpl( serviceRegistry );
|
||||
|
||||
this.defaultTimezoneStorage = resolveTimeZoneStorageStrategy( configService );
|
||||
this.wrapperArrayHandling = resolveWrapperArrayHandling( configService );
|
||||
this.multiTenancyEnabled = JdbcEnvironmentImpl.isMultiTenancyEnabled( serviceRegistry );
|
||||
|
||||
this.xmlMappingEnabled = configService.getSetting(
|
||||
|
@ -868,6 +873,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrapperArrayHandling getWrapperArrayHandling() {
|
||||
return wrapperArrayHandling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BasicTypeRegistration> getBasicTypeRegistrations() {
|
||||
return basicTypeRegistrations;
|
||||
|
@ -999,4 +1009,21 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ import org.hibernate.mapping.Table;
|
|||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.BasicTypeRegistry;
|
||||
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.jdbc.JdbcType;
|
||||
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
|
||||
final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect();
|
||||
dialect.contribute( typeContributions, options.getServiceRegistry() );
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.cache.spi.access.AccessType;
|
|||
import org.hibernate.cfg.MetadataSourceType;
|
||||
import org.hibernate.dialect.TimeZoneSupport;
|
||||
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.SharedCacheMode;
|
||||
|
@ -67,6 +68,11 @@ public abstract class AbstractDelegatingMetadataBuildingOptions implements Metad
|
|||
return delegate.getTimeZoneSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrapperArrayHandling getWrapperArrayHandling() {
|
||||
return delegate.getWrapperArrayHandling();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BasicTypeRegistration> getBasicTypeRegistrations() {
|
||||
return delegate.getBasicTypeRegistrations();
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
|
|||
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||
import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard;
|
||||
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.SharedCacheMode;
|
||||
|
@ -70,6 +71,13 @@ public interface MetadataBuildingOptions {
|
|||
*/
|
||||
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() {
|
||||
// for now always return the standard one
|
||||
return ManagedTypeRepresentationResolverStandard.INSTANCE;
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
|
|||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.CustomType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||
import org.hibernate.type.descriptor.java.BasicJavaType;
|
||||
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
|
||||
|
@ -857,6 +858,18 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||
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
|
||||
* boot-time model into the run-time model
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.hibernate.Internal;
|
|||
import org.hibernate.MappingException;
|
||||
import org.hibernate.boot.model.relational.Database;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.engine.spi.CascadeStyle;
|
||||
import org.hibernate.engine.spi.CascadeStyles;
|
||||
import org.hibernate.engine.spi.Mapping;
|
||||
|
@ -33,6 +34,7 @@ import org.hibernate.generator.Generator;
|
|||
import org.hibernate.generator.GeneratorCreationContext;
|
||||
import org.hibernate.type.CompositeType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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() {
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
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
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.sql.Statement;
|
|||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
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.RequiresDialectFeature;
|
||||
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.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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 }
|
||||
)
|
||||
@SessionFactory
|
||||
@ServiceRegistry(
|
||||
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
|
||||
)
|
||||
public class BasicOperationsTest {
|
||||
|
||||
private static final String SOME_ENTITY_TABLE_NAME = "SOMEENTITY";
|
||||
|
|
|
@ -9,8 +9,11 @@ package org.hibernate.orm.test.annotations.lob;
|
|||
import java.util.Arrays;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.dialect.SybaseDialect;
|
||||
import org.hibernate.type.WrapperArrayHandling;
|
||||
|
||||
import org.hibernate.testing.RequiresDialect;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
@ -28,6 +31,12 @@ import junit.framework.AssertionFailedError;
|
|||
public class ImageTest extends BaseCoreFunctionalTestCase {
|
||||
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
|
||||
public void testBoundedLongByteArrayAccess() {
|
||||
byte[] original = buildRecursively(ARRAY_SIZE, true);
|
||||
|
|
|
@ -13,6 +13,7 @@ import jakarta.persistence.Lob;
|
|||
import jakarta.persistence.Table;
|
||||
|
||||
import org.hibernate.annotations.JavaType;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
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.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;
|
||||
|
||||
|
@ -40,6 +43,9 @@ import static org.hamcrest.Matchers.isOneOf;
|
|||
*/
|
||||
@DomainModel(annotatedClasses = ByteArrayMappingTests.EntityOfByteArrays.class)
|
||||
@SessionFactory
|
||||
@ServiceRegistry(
|
||||
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
|
||||
)
|
||||
public class ByteArrayMappingTests {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -13,6 +13,7 @@ import jakarta.persistence.Lob;
|
|||
import jakarta.persistence.Table;
|
||||
|
||||
import org.hibernate.annotations.JavaType;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
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.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.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -39,6 +42,9 @@ import static org.hamcrest.Matchers.isOneOf;
|
|||
*/
|
||||
@DomainModel(annotatedClasses = CharacterArrayMappingTests.EntityWithCharArrays.class)
|
||||
@SessionFactory
|
||||
@ServiceRegistry(
|
||||
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
|
||||
)
|
||||
public class CharacterArrayMappingTests {
|
||||
@Test
|
||||
public void verifyMappings(SessionFactoryScope scope) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import jakarta.persistence.Table;
|
|||
|
||||
import org.hibernate.annotations.JavaType;
|
||||
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;
|
||||
|
@ -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.DomainModel;
|
||||
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.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -45,6 +48,9 @@ import static org.hamcrest.Matchers.isOneOf;
|
|||
@DomainModel(annotatedClasses = CharacterArrayNationalizedMappingTests.EntityWithCharArrays.class)
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsNationalizedData.class)
|
||||
@ServiceRegistry(
|
||||
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
|
||||
)
|
||||
public class CharacterArrayNationalizedMappingTests {
|
||||
@Test
|
||||
public void verifyMappings(SessionFactoryScope scope) {
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -114,22 +114,23 @@ To revert to Hibernate ORM 5's behavior, set the configuration property `hiberna
|
|||
[[byte-and-character-array-mapping-changes]]
|
||||
== Byte[]/Character[] mapping changes
|
||||
|
||||
The mappings for the wrapper array types `Byte[]` and `Character[]` have changed.
|
||||
Hibernate historically allowed mapping `Byte[]` and `Character[]` in a domain model as basic values to
|
||||
`VARBINARY` and `(N)VARCHAR` SQL types.
|
||||
|
||||
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.
|
||||
Similarly, `Character[]` was blindly converted to `char[]`, potentially running into the same `NullPointerException` problem.
|
||||
Strictly speaking, this is an inaccurate mapping. Because the Java wrapper types (`Byte` and `Character`) are used, null
|
||||
elements are allowed. However, it is not possible to store such domain values as `VARBINARY` and `(N)VARCHAR` SQL types.
|
||||
In fact, attempting to store such values leads to errors on previous versions. The legacy support has an implicit contract
|
||||
that the `Byte[]` and `Character[]` types are handled exactly the same as the `byte[]` and `char[]` variants.
|
||||
|
||||
Since disallowing `null` elements defeats the purpose of choosing the wrapper array type in the first place,
|
||||
the Hibernate team concluded that changing the mapping for these types is fine,
|
||||
because there can not be any reasonable real world uses of `Byte[]` and `Character[]`.
|
||||
Building on the link:{docsBase}/6.1/migration-guide/migration-guide.html#basic-arraycollection-mapping[ability] to use
|
||||
structured SQL types (`ARRAY`, `SQLXML`, ...) for storing basic values, 6.2 makes it configurable how to handle mappings of
|
||||
this type:
|
||||
|
||||
Another motivation for fixing this is that array handling code currently has to special case the element types
|
||||
`Byte` and `Character`, and that binding SQL arrays of these types is not possible for native queries.
|
||||
DISALLOW:: (default) Throw an informative and actionable error
|
||||
ALLOW:: Allows the use of the wrapper arrays stored as structured SQL types (`ARRAY`, `SQLXML`, ...) to maintain proper null element semantics.
|
||||
LEGACY:: Allows the use of the wrapper arrays stored as `VARBINARY` and `VARCHAR`, disallowing null elements.
|
||||
|
||||
Starting with Hibernate 6.2, `Byte[]` and `Character[]` will be treated like other arrays.
|
||||
See the link:{docsBase}/6.1/migration-guide/migration-guide.html#basic-arraycollection-mapping[6.1 Migration guide] for
|
||||
details about the default DDL mapping.
|
||||
See link:{javadocsBase}/org/hibernate/cfg/AvailableSettings.html#WRAPPER_ARRAY_HANDLING[AvailableSettings#WRAPPER_ARRAY_HANDLING]
|
||||
|
||||
A possible migration could involve the following steps in a migration script:
|
||||
|
||||
|
@ -139,7 +140,7 @@ A possible migration could involve the following steps in a migration script:
|
|||
* For every result, load the Hibernate entity by primary key and set the field value to transformed result `Byte[]` or `Character[]`
|
||||
* Finally, drop the old column `alter table tbl drop column array_col_old`
|
||||
|
||||
Alternatively, to revert to pre-6.2 behavior, annotate your array property with `@JavaType(ByteArrayJavaType.class)`
|
||||
Alternatively, to revert to pre-6.2 behavior for specific properties, annotate your array property with `@JavaType(ByteArrayJavaType.class)`
|
||||
or `@JavaType(CharacterArrayJavaType.class)` or simply change the domain model type to `byte[]` and `char[]` respectively.
|
||||
|
||||
[[ddl-check]]
|
||||
|
|
Loading…
Reference in New Issue