HHH-15828 fix setting TIMEZONE_DEFAULT_STORAGE to COLUMN

This commit is contained in:
Gavin 2022-12-06 17:40:58 +01:00 committed by Gavin King
parent 4fba6ac60d
commit ee66a93302
8 changed files with 164 additions and 136 deletions

View File

@ -746,10 +746,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
@Override @Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorage() { public TimeZoneStorageStrategy getDefaultTimeZoneStorage() {
return toTimeZoneStorageStrategy( getTimeZoneSupport( serviceRegistry ) ); return toTimeZoneStorageStrategy( getTimeZoneSupport() );
} }
private static TimeZoneSupport getTimeZoneSupport(StandardServiceRegistry serviceRegistry) { @Override
public TimeZoneSupport getTimeZoneSupport() {
try { try {
return serviceRegistry.getService( JdbcServices.class ) return serviceRegistry.getService( JdbcServices.class )
.getDialect() .getDialect()
@ -759,6 +760,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
return TimeZoneSupport.NONE; return TimeZoneSupport.NONE;
} }
} }
private TimeZoneStorageStrategy toTimeZoneStorageStrategy(TimeZoneSupport timeZoneSupport) { private TimeZoneStorageStrategy toTimeZoneStorageStrategy(TimeZoneSupport timeZoneSupport) {
switch ( defaultTimezoneStorage ) { switch ( defaultTimezoneStorage ) {
case NATIVE: case NATIVE:
@ -775,9 +777,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
case AUTO: case AUTO:
switch (timeZoneSupport) { switch (timeZoneSupport) {
case NATIVE: case NATIVE:
// if the db has native support for timezones, we use that, not a column
return TimeZoneStorageStrategy.NATIVE; return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE: case NORMALIZE:
case NONE: case NONE:
// otherwise we use a separate column
return TimeZoneStorageStrategy.COLUMN; return TimeZoneStorageStrategy.COLUMN;
default: default:
throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport); throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport);
@ -785,9 +789,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
case DEFAULT: case DEFAULT:
switch (timeZoneSupport) { switch (timeZoneSupport) {
case NATIVE: case NATIVE:
// if the db has native support for timezones, we use that, and don't normalize
return TimeZoneStorageStrategy.NATIVE; return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE: case NORMALIZE:
case NONE: case NONE:
// otherwise we normalize things to UTC
return TimeZoneStorageStrategy.NORMALIZE_UTC; return TimeZoneStorageStrategy.NORMALIZE_UTC;
default: default:
throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport); throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport);

View File

@ -66,7 +66,7 @@ import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferre
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForUuid; import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForUuid;
/** /**
* Represents the process of of transforming a {@link MetadataSources} * Represents the process of transforming a {@link MetadataSources}
* reference into a {@link org.hibernate.boot.Metadata} reference. Allows for 2 different process paradigms:<ul> * reference into a {@link org.hibernate.boot.Metadata} reference. Allows for 2 different process paradigms:<ul>
* <li> * <li>
* Single step : as defined by the {@link #build} method; internally leverages the 2-step paradigm * Single step : as defined by the {@link #build} method; internally leverages the 2-step paradigm
@ -465,7 +465,7 @@ public class MetadataBuildingProcess {
if ( timestampWithTimeZoneOverride != null ) { if ( timestampWithTimeZoneOverride != null ) {
adaptToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride ); adaptToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride );
} }
final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant(serviceRegistry); final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant( serviceRegistry );
if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) { if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) {
adaptToPreferredSqlTypeCodeForInstant( typeConfiguration, jdbcTypeRegistry, preferredSqlTypeCodeForInstant ); adaptToPreferredSqlTypeCodeForInstant( typeConfiguration, jdbcTypeRegistry, preferredSqlTypeCodeForInstant );
} }

View File

@ -16,6 +16,7 @@ import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.cache.spi.access.AccessType; 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.id.factory.IdentifierGeneratorFactory; import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -58,6 +59,11 @@ public abstract class AbstractDelegatingMetadataBuildingOptions implements Metad
return delegate.getDefaultTimeZoneStorage(); return delegate.getDefaultTimeZoneStorage();
} }
@Override
public TimeZoneSupport getTimeZoneSupport() {
return delegate.getTimeZoneSupport();
}
@Override @Override
public List<BasicTypeRegistration> getBasicTypeRegistrations() { public List<BasicTypeRegistration> getBasicTypeRegistrations() {
return delegate.getBasicTypeRegistrations(); return delegate.getBasicTypeRegistrations();

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.collection.internal.StandardCollectionSemanticsResolver; import org.hibernate.collection.internal.StandardCollectionSemanticsResolver;
import org.hibernate.collection.spi.CollectionSemanticsResolver; import org.hibernate.collection.spi.CollectionSemanticsResolver;
import org.hibernate.dialect.TimeZoneSupport;
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;
@ -51,6 +52,8 @@ public interface MetadataBuildingOptions {
TimeZoneStorageStrategy getDefaultTimeZoneStorage(); TimeZoneStorageStrategy getDefaultTimeZoneStorage();
TimeZoneSupport getTimeZoneSupport();
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

@ -26,12 +26,9 @@ import jakarta.persistence.MappedSuperclass;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.Internal;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.ColumnTransformer; import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.ColumnTransformers; import org.hibernate.annotations.ColumnTransformers;
import org.hibernate.annotations.TimeZoneColumn; import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.common.reflection.XAnnotatedElement; import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
@ -47,6 +44,8 @@ import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.hibernate.cfg.AnnotationBinder.useColumnForTimeZoneStorage;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
@ -64,8 +63,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
private Map<String, JoinTable> currentPropertyJoinTableOverride; private Map<String, JoinTable> currentPropertyJoinTableOverride;
private Map<String, ForeignKey> holderForeignKeyOverride; private Map<String, ForeignKey> holderForeignKeyOverride;
private Map<String, ForeignKey> currentPropertyForeignKeyOverride; private Map<String, ForeignKey> currentPropertyForeignKeyOverride;
private String path; private final String path;
private MetadataBuildingContext context; private final MetadataBuildingContext context;
private Boolean isInIdClass; private Boolean isInIdClass;
AbstractPropertyHolder( AbstractPropertyHolder(
@ -192,7 +191,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
this.currentPropertyColumnOverride = null; this.currentPropertyColumnOverride = null;
} }
this.currentPropertyColumnTransformerOverride = buildColumnTransformerOverride( property, getPath() ); this.currentPropertyColumnTransformerOverride = buildColumnTransformerOverride( property );
if ( this.currentPropertyColumnTransformerOverride.size() == 0 ) { if ( this.currentPropertyColumnTransformerOverride.size() == 0 ) {
this.currentPropertyColumnTransformerOverride = null; this.currentPropertyColumnTransformerOverride = null;
} }
@ -217,7 +216,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
/** /**
* Get column overriding, property first, then parent, then holder * Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&amp;&amp;element' with nothing * replace the placeholder 'collection&amp;&amp;element' with nothing
* * <p>
* These rules are here to support both JPA 2 and legacy overriding rules. * These rules are here to support both JPA 2 and legacy overriding rules.
*/ */
@Override @Override
@ -416,7 +415,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|| current.isAnnotationPresent( Embeddable.class ) ) { || current.isAnnotationPresent( Embeddable.class ) ) {
//FIXME is embeddable override? //FIXME is embeddable override?
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath(), context ); Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath(), context );
Map<String, ColumnTransformer> currentTransformerOverride = buildColumnTransformerOverride( current, getPath() ); Map<String, ColumnTransformer> currentTransformerOverride = buildColumnTransformerOverride( current );
Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() ); Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() );
Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() ); Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
Map<String, ForeignKey> currentForeignKeyOverride = buildForeignKeyOverride( current, getPath() ); Map<String, ForeignKey> currentForeignKeyOverride = buildForeignKeyOverride( current, getPath() );
@ -477,105 +476,97 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
} }
} }
for (Map.Entry<String, List<Column>> entry : columnOverrideList.entrySet()) { for ( Map.Entry<String, List<Column>> entry : columnOverrideList.entrySet() ) {
columnOverride.put( columnOverride.put( entry.getKey(), entry.getValue().toArray( new Column[0] ) );
entry.getKey(),
entry.getValue().toArray( new Column[entry.getValue().size()] )
);
} }
} }
else { else if ( useColumnForTimeZoneStorage( element, context ) ) {
final TimeZoneStorage timeZoneStorage = element.getAnnotation( TimeZoneStorage.class ); final Column column = createTimestampColumn( element, path, context );
if ( timeZoneStorage != null ) { columnOverride.put(
switch ( timeZoneStorage.value() ) { path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
case AUTO: new Column[]{ column }
if ( context.getBuildingOptions().getDefaultTimeZoneStorage() != TimeZoneStorageStrategy.COLUMN ) { );
break; final Column offsetColumn = createTimeZoneColumn( element, column );
} columnOverride.put(
case COLUMN: path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME,
final Column column; new Column[]{ offsetColumn }
final Column annotatedColumn = element.getAnnotation( Column.class ); );
if ( annotatedColumn != null ) {
column = annotatedColumn;
}
else {
// Base the name of the synthetic dateTime field on the name of the original attribute
final Identifier implicitName = context.getObjectNameNormalizer().normalizeIdentifierQuoting(
context.getBuildingOptions().getImplicitNamingStrategy().determineBasicColumnName(
new ImplicitBasicColumnNameSource() {
final AttributePath attributePath = AttributePath.parse( path );
@Override
public AttributePath getAttributePath() {
return attributePath;
}
@Override
public boolean isCollectionElement() {
return false;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return context;
}
}
)
);
column = new ColumnImpl(
implicitName.getText(),
false,
true,
true,
true,
"",
"",
0
);
}
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
new Column[] { column }
);
final Column offsetColumn;
final TimeZoneColumn timeZoneColumn = element.getAnnotation( TimeZoneColumn.class );
if ( timeZoneColumn != null ) {
offsetColumn = new ColumnImpl(
timeZoneColumn.name(),
false,
column.nullable(),
timeZoneColumn.insertable(),
timeZoneColumn.updatable(),
timeZoneColumn.columnDefinition(),
timeZoneColumn.table(),
0
);
}
else {
offsetColumn = new ColumnImpl(
column.name() + "_tz",
false,
column.nullable(),
column.insertable(),
column.updatable(),
"",
column.table(),
0
);
}
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME,
new Column[] { offsetColumn }
);
break;
}
}
} }
} }
return columnOverride; return columnOverride;
} }
private static Map<String, ColumnTransformer> buildColumnTransformerOverride(XAnnotatedElement element, String path) { private static Column createTimeZoneColumn(XAnnotatedElement element, Column column) {
final TimeZoneColumn timeZoneColumn = element.getAnnotation( TimeZoneColumn.class );
if ( timeZoneColumn != null ) {
return new ColumnImpl(
timeZoneColumn.name(),
false,
column.nullable(),
timeZoneColumn.insertable(),
timeZoneColumn.updatable(),
timeZoneColumn.columnDefinition(),
timeZoneColumn.table(),
0
);
}
else {
return new ColumnImpl(
column.name() + "_tz",
false,
column.nullable(),
column.insertable(),
column.updatable(),
"",
column.table(),
0
);
}
}
private static Column createTimestampColumn(XAnnotatedElement element, String path, MetadataBuildingContext context) {
final Column annotatedColumn = element.getAnnotation( Column.class );
if ( annotatedColumn != null ) {
return annotatedColumn;
}
else {
// Base the name of the synthetic dateTime field on the name of the original attribute
final Identifier implicitName = context.getObjectNameNormalizer().normalizeIdentifierQuoting(
context.getBuildingOptions().getImplicitNamingStrategy().determineBasicColumnName(
new ImplicitBasicColumnNameSource() {
final AttributePath attributePath = AttributePath.parse(path);
@Override
public AttributePath getAttributePath() {
return attributePath;
}
@Override
public boolean isCollectionElement() {
return false;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return context;
}
}
)
);
return new ColumnImpl(
implicitName.getText(),
false,
true,
true,
true,
"",
"",
0
);
}
}
private static Map<String, ColumnTransformer> buildColumnTransformerOverride(XAnnotatedElement element) {
Map<String, ColumnTransformer> columnOverride = new HashMap<>(); Map<String, ColumnTransformer> columnOverride = new HashMap<>();
if ( element != null ) { if ( element != null ) {
ColumnTransformer singleOverride = element.getAnnotation( ColumnTransformer.class ); ColumnTransformer singleOverride = element.getAnnotation( ColumnTransformer.class );

View File

@ -9,6 +9,8 @@ package org.hibernate.cfg;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -74,6 +76,7 @@ import org.hibernate.cfg.annotations.EntityBinder;
import org.hibernate.cfg.annotations.Nullability; import org.hibernate.cfg.annotations.Nullability;
import org.hibernate.cfg.annotations.PropertyBinder; import org.hibernate.cfg.annotations.PropertyBinder;
import org.hibernate.cfg.annotations.QueryBinder; import org.hibernate.cfg.annotations.QueryBinder;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
@ -171,6 +174,9 @@ import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
public final class AnnotationBinder { public final class AnnotationBinder {
private static final CoreMessageLogger LOG = messageLogger( AnnotationBinder.class ); private static final CoreMessageLogger LOG = messageLogger( AnnotationBinder.class );
private static final String OFFSET_DATETIME_CLASS = OffsetDateTime.class.getName();
private static final String ZONED_DATETIME_CLASS = ZonedDateTime.class.getName();
private AnnotationBinder() {} private AnnotationBinder() {}
public static void bindDefaults(MetadataBuildingContext context) { public static void bindDefaults(MetadataBuildingContext context) {
@ -1780,31 +1786,6 @@ public final class AnnotationBinder {
return null; return null;
} }
static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( property != null ) {
final TimeZoneStorage timeZoneStorage = property.getAnnotation( TimeZoneStorage.class );
if ( timeZoneStorage != null ) {
switch ( timeZoneStorage.value() ) {
case AUTO:
if ( context.getBuildingOptions().getDefaultTimeZoneStorage() != TimeZoneStorageStrategy.COLUMN ) {
return null;
}
case COLUMN:
switch ( returnedClass.getName() ) {
case "java.time.OffsetDateTime":
return OffsetDateTimeCompositeUserType.class;
case "java.time.ZonedDateTime":
return ZonedDateTimeCompositeUserType.class;
}
}
}
}
return null;
}
private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) { private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) {
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled(); return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
} }
@ -2495,4 +2476,51 @@ public final class AnnotationBinder {
LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); LOG.ignoreNotFoundWithFetchTypeLazy( entity, association );
} }
} }
private static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( useColumnForTimeZoneStorage( property, context ) ) {
String returnedClassName = returnedClass.getName();
if ( OFFSET_DATETIME_CLASS.equals( returnedClassName ) ) {
return OffsetDateTimeCompositeUserType.class;
}
else if ( ZONED_DATETIME_CLASS.equals( returnedClassName ) ) {
return ZonedDateTimeCompositeUserType.class;
}
}
return null;
}
private static boolean isZonedDateTimeClass(String returnedClassName) {
return OFFSET_DATETIME_CLASS.equals( returnedClassName )
|| ZONED_DATETIME_CLASS.equals( returnedClassName );
}
static boolean useColumnForTimeZoneStorage(XAnnotatedElement element, MetadataBuildingContext context) {
final TimeZoneStorage timeZoneStorage = element.getAnnotation( TimeZoneStorage.class );
if ( timeZoneStorage == null ) {
if ( element instanceof XProperty ) {
XProperty property = (XProperty) element;
return isZonedDateTimeClass( property.getType().getName() )
//no @TimeZoneStorage annotation, so we need to use the default storage strategy
&& context.getBuildingOptions().getDefaultTimeZoneStorage() == TimeZoneStorageStrategy.COLUMN;
}
else {
return false;
}
}
else {
switch ( timeZoneStorage.value() ) {
case COLUMN:
return true;
case AUTO:
// if the db has native support for timezones, we use that, not a column
return context.getBuildingOptions().getTimeZoneSupport() != TimeZoneSupport.NATIVE;
default:
return false;
}
}
}
} }

View File

@ -930,9 +930,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
final Target targetAnn = findAnnotation( attributeXProperty, Target.class ); final Target targetAnn = findAnnotation( attributeXProperty, Target.class );
if ( targetAnn != null ) { if ( targetAnn != null ) {
return (BasicJavaType<?>) typeConfiguration return (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( targetAnn.value() );
.getJavaTypeRegistry()
.getDescriptor( targetAnn.value() );
} }
return null; return null;
@ -965,9 +963,6 @@ public class BasicValueBinder implements JdbcTypeIndicators {
} }
} }
} }
else {
timeZoneStorageType = null;
}
} }
private static Class<? extends UserType<?>> normalizeUserType(Class<? extends UserType<?>> userType) { private static Class<? extends UserType<?>> normalizeUserType(Class<? extends UserType<?>> userType) {

View File

@ -13,7 +13,6 @@ import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueBinder;