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
public TimeZoneStorageStrategy getDefaultTimeZoneStorage() {
return toTimeZoneStorageStrategy( getTimeZoneSupport( serviceRegistry ) );
return toTimeZoneStorageStrategy( getTimeZoneSupport() );
}
private static TimeZoneSupport getTimeZoneSupport(StandardServiceRegistry serviceRegistry) {
@Override
public TimeZoneSupport getTimeZoneSupport() {
try {
return serviceRegistry.getService( JdbcServices.class )
.getDialect()
@ -759,6 +760,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
return TimeZoneSupport.NONE;
}
}
private TimeZoneStorageStrategy toTimeZoneStorageStrategy(TimeZoneSupport timeZoneSupport) {
switch ( defaultTimezoneStorage ) {
case NATIVE:
@ -775,9 +777,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
case AUTO:
switch (timeZoneSupport) {
case NATIVE:
// if the db has native support for timezones, we use that, not a column
return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE:
case NONE:
// otherwise we use a separate column
return TimeZoneStorageStrategy.COLUMN;
default:
throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport);
@ -785,9 +789,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
case DEFAULT:
switch (timeZoneSupport) {
case NATIVE:
// if the db has native support for timezones, we use that, and don't normalize
return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE:
case NONE:
// otherwise we normalize things to UTC
return TimeZoneStorageStrategy.NORMALIZE_UTC;
default:
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;
/**
* 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>
* <li>
* 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 ) {
adaptToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride );
}
final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant(serviceRegistry);
final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant( serviceRegistry );
if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) {
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.cache.spi.access.AccessType;
import org.hibernate.cfg.MetadataSourceType;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.type.spi.TypeConfiguration;
@ -58,6 +59,11 @@ public abstract class AbstractDelegatingMetadataBuildingOptions implements Metad
return delegate.getDefaultTimeZoneStorage();
}
@Override
public TimeZoneSupport getTimeZoneSupport() {
return delegate.getTimeZoneSupport();
}
@Override
public List<BasicTypeRegistration> 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.collection.internal.StandardCollectionSemanticsResolver;
import org.hibernate.collection.spi.CollectionSemanticsResolver;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard;
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
@ -51,6 +52,8 @@ public interface MetadataBuildingOptions {
TimeZoneStorageStrategy getDefaultTimeZoneStorage();
TimeZoneSupport getTimeZoneSupport();
default ManagedTypeRepresentationResolver getManagedTypeRepresentationResolver() {
// for now always return the standard one
return ManagedTypeRepresentationResolverStandard.INSTANCE;

View File

@ -26,12 +26,9 @@ import jakarta.persistence.MappedSuperclass;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.Internal;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.ColumnTransformers;
import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
@ -47,6 +44,8 @@ import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import org.jboss.logging.Logger;
import static org.hibernate.cfg.AnnotationBinder.useColumnForTimeZoneStorage;
/**
* @author Emmanuel Bernard
*/
@ -64,8 +63,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
private Map<String, JoinTable> currentPropertyJoinTableOverride;
private Map<String, ForeignKey> holderForeignKeyOverride;
private Map<String, ForeignKey> currentPropertyForeignKeyOverride;
private String path;
private MetadataBuildingContext context;
private final String path;
private final MetadataBuildingContext context;
private Boolean isInIdClass;
AbstractPropertyHolder(
@ -192,7 +191,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
this.currentPropertyColumnOverride = null;
}
this.currentPropertyColumnTransformerOverride = buildColumnTransformerOverride( property, getPath() );
this.currentPropertyColumnTransformerOverride = buildColumnTransformerOverride( property );
if ( this.currentPropertyColumnTransformerOverride.size() == 0 ) {
this.currentPropertyColumnTransformerOverride = null;
}
@ -217,7 +216,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
/**
* Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&amp;&amp;element' with nothing
*
* <p>
* These rules are here to support both JPA 2 and legacy overriding rules.
*/
@Override
@ -416,7 +415,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|| current.isAnnotationPresent( Embeddable.class ) ) {
//FIXME is embeddable override?
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, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
Map<String, ForeignKey> currentForeignKeyOverride = buildForeignKeyOverride( current, getPath() );
@ -477,33 +476,65 @@ 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( entry.getKey(), entry.getValue().toArray( new Column[0] ) );
}
}
else if ( useColumnForTimeZoneStorage( element, context ) ) {
final Column column = createTimestampColumn( element, path, context );
columnOverride.put(
entry.getKey(),
entry.getValue().toArray( new Column[entry.getValue().size()] )
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
new Column[]{ column }
);
final Column offsetColumn = createTimeZoneColumn( element, column );
columnOverride.put(
path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME,
new Column[]{ offsetColumn }
);
}
}
else {
final TimeZoneStorage timeZoneStorage = element.getAnnotation( TimeZoneStorage.class );
if ( timeZoneStorage != null ) {
switch ( timeZoneStorage.value() ) {
case AUTO:
if ( context.getBuildingOptions().getDefaultTimeZoneStorage() != TimeZoneStorageStrategy.COLUMN ) {
break;
return columnOverride;
}
case COLUMN:
final Column column;
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 ) {
column = annotatedColumn;
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 );
final AttributePath attributePath = AttributePath.parse(path);
@Override
public AttributePath getAttributePath() {
@ -522,7 +553,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
}
)
);
column = new ColumnImpl(
return new ColumnImpl(
implicitName.getText(),
false,
true,
@ -533,49 +564,9 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
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;
}
private static Map<String, ColumnTransformer> buildColumnTransformerOverride(XAnnotatedElement element, String path) {
private static Map<String, ColumnTransformer> buildColumnTransformerOverride(XAnnotatedElement element) {
Map<String, ColumnTransformer> columnOverride = new HashMap<>();
if ( element != null ) {
ColumnTransformer singleOverride = element.getAnnotation( ColumnTransformer.class );

View File

@ -9,6 +9,8 @@ package org.hibernate.cfg;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
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.PropertyBinder;
import org.hibernate.cfg.annotations.QueryBinder;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger;
@ -171,6 +174,9 @@ import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
public final class AnnotationBinder {
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() {}
public static void bindDefaults(MetadataBuildingContext context) {
@ -1780,31 +1786,6 @@ public final class AnnotationBinder {
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) {
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
}
@ -2495,4 +2476,51 @@ public final class AnnotationBinder {
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 );
if ( targetAnn != null ) {
return (BasicJavaType<?>) typeConfiguration
.getJavaTypeRegistry()
.getDescriptor( targetAnn.value() );
return (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( targetAnn.value() );
}
return null;
@ -965,9 +963,6 @@ public class BasicValueBinder implements JdbcTypeIndicators {
}
}
}
else {
timeZoneStorageType = null;
}
}
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.Types;
import java.time.Instant;
import java.time.OffsetDateTime;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;