big refactoring of Binders

This commit is contained in:
Gavin 2022-12-26 15:09:09 +01:00 committed by Gavin King
parent dda88668e8
commit c9cd12c625
22 changed files with 2407 additions and 2271 deletions

View File

@ -45,7 +45,7 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable; import jakarta.persistence.JoinTable;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import static org.hibernate.boot.model.internal.AnnotationBinder.useColumnForTimeZoneStorage; import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.useColumnForTimeZoneStorage;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard

View File

@ -0,0 +1,126 @@
/*
* 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.boot.model.internal;
import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinTable;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.Property;
import java.util.Locale;
import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy;
import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation;
import static org.hibernate.boot.model.internal.BinderHelper.getPath;
public class AnyBinder {
static void bindAny(
PropertyHolder propertyHolder,
Nullability nullability,
PropertyData inferredData,
EntityBinder entityBinder,
boolean isIdentifierMapper,
MetadataBuildingContext context,
XProperty property,
AnnotatedJoinColumns joinColumns,
boolean forcePersist) {
//check validity
if ( property.isAnnotationPresent( Columns.class ) ) {
throw new AnnotationException(
String.format(
Locale.ROOT,
"Property '%s' is annotated '@Any' and may not have a '@Columns' annotation "
+ "(a single '@Column' or '@Formula' must be used to map the discriminator, and '@JoinColumn's must be used to map the foreign key) ",
getPath( propertyHolder, inferredData )
)
);
}
final Cascade hibernateCascade = property.getAnnotation( Cascade.class );
final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
final JoinTable assocTable = propertyHolder.getJoinTable(property);
if ( assocTable != null ) {
final Join join = propertyHolder.addJoin( assocTable, false );
for ( AnnotatedJoinColumn joinColumn : joinColumns.getJoinColumns() ) {
joinColumn.setExplicitTableName( join.getTable().getName() );
}
}
bindAny(
getCascadeStrategy( null, hibernateCascade, false, forcePersist ),
//@Any has no cascade attribute
joinColumns,
onDeleteAnn == null ? null : onDeleteAnn.action(),
nullability,
propertyHolder,
inferredData,
entityBinder,
isIdentifierMapper,
context
);
}
private static void bindAny(
String cascadeStrategy,
AnnotatedJoinColumns columns,
OnDeleteAction onDeleteAction,
Nullability nullability,
PropertyHolder propertyHolder,
PropertyData inferredData,
EntityBinder entityBinder,
boolean isIdentifierMapper,
MetadataBuildingContext context) {
final XProperty property = inferredData.getProperty();
final org.hibernate.annotations.Any any = property.getAnnotation( org.hibernate.annotations.Any.class );
if ( any == null ) {
throw new AssertionFailure( "Missing @Any annotation: " + getPath( propertyHolder, inferredData ) );
}
final boolean lazy = any.fetch() == FetchType.LAZY;
final Any value = BinderHelper.buildAnyValue(
property.getAnnotation( Column.class ),
getOverridableAnnotation( property, Formula.class, context ),
columns,
inferredData,
onDeleteAction,
lazy,
nullability,
propertyHolder,
entityBinder,
any.optional(),
context
);
final PropertyBinder binder = new PropertyBinder();
binder.setName( inferredData.getPropertyName() );
binder.setValue( value );
binder.setLazy( lazy );
//binder.setCascade(cascadeStrategy);
if ( isIdentifierMapper ) {
binder.setInsertable( false );
binder.setUpdatable( false );
}
binder.setAccessType( inferredData.getDefaultAccess() );
binder.setCascade( cascadeStrategy );
Property prop = binder.makeProperty();
//composite FK columns are in the same table, so it's OK
propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() );
}
}

View File

@ -14,8 +14,6 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import jakarta.persistence.Basic;
import jakarta.persistence.FetchType;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.MappingException; import org.hibernate.MappingException;
@ -96,6 +94,8 @@ import jakarta.persistence.Version;
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation; import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
/** /**
* A stateful binder responsible for creating instances of {@link BasicValue}.
*
* @author Steve Ebersole * @author Steve Ebersole
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
@ -107,26 +107,6 @@ public class BasicValueBinder implements JdbcTypeIndicators {
private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BasicValueBinder.class.getName() ); private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BasicValueBinder.class.getName() );
static boolean isOptional(XProperty property) {
if ( property.isAnnotationPresent( Basic.class ) ) {
final Basic basic = property.getAnnotation( Basic.class );
return basic.optional();
}
else {
return property.isArray() || !property.getClassOrElementClass().isPrimitive();
}
}
static boolean isLazy(XProperty property) {
if ( property.isAnnotationPresent( Basic.class ) ) {
final Basic basic = property.getAnnotation( Basic.class );
return basic.fetch() == FetchType.LAZY;
}
else {
return false;
}
}
public enum Kind { public enum Kind {
ATTRIBUTE( ValueMappingAccess.INSTANCE ), ATTRIBUTE( ValueMappingAccess.INSTANCE ),
ANY_DISCRIMINATOR( AnyDiscriminatorMappingAccess.INSTANCE ), ANY_DISCRIMINATOR( AnyDiscriminatorMappingAccess.INSTANCE ),

View File

@ -22,6 +22,8 @@ import java.util.StringTokenizer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
@ -37,14 +39,11 @@ import org.hibernate.annotations.SqlFragmentAlias;
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;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData; import org.hibernate.boot.spi.PropertyData;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.internal.CoreLogging;
import org.hibernate.mapping.Any; import org.hibernate.mapping.Any;
import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
@ -55,18 +54,13 @@ import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.SyntheticProperty; import org.hibernate.mapping.SyntheticProperty;
import org.hibernate.mapping.Table; import org.hibernate.mapping.Table;
import org.hibernate.mapping.ToOne; import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value; import org.hibernate.mapping.Value;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.jboss.logging.Logger;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
@ -74,7 +68,6 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnOrFor
import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED;
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP;
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.interpret; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.interpret;
@ -84,8 +77,6 @@ import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.
*/ */
public class BinderHelper { public class BinderHelper {
private static final Logger log = CoreLogging.logger( BinderHelper.class );
private BinderHelper() { private BinderHelper() {
} }
@ -597,7 +588,7 @@ public class BinderHelper {
} }
} }
} }
catch (MappingException e) { catch ( MappingException e ) {
try { try {
//if we do not find it try to check the identifier mapper //if we do not find it try to check the identifier mapper
if ( associatedClass.getIdentifierMapper() == null ) { if ( associatedClass.getIdentifierMapper() == null ) {
@ -617,7 +608,7 @@ public class BinderHelper {
} }
} }
} }
catch (MappingException ee) { catch ( MappingException ee ) {
return null; return null;
} }
} }
@ -731,129 +722,6 @@ public class BinderHelper {
return null; return null;
} }
/**
* Apply an id generation strategy and parameters to the
* given {@link SimpleValue} which represents an identifier.
*/
public static void makeIdGenerator(
SimpleValue id,
XProperty property,
String generatorType,
String generatorName,
MetadataBuildingContext buildingContext,
Map<String, IdentifierGeneratorDefinition> localGenerators) {
log.debugf( "#makeIdGenerator(%s, %s, %s, %s, ...)", id, property, generatorType, generatorName );
final Table table = id.getTable();
table.setIdentifierValue( id );
//generator settings
id.setIdentifierGeneratorStrategy( generatorType );
final Map<String,Object> parameters = new HashMap<>();
//always settable
parameters.put( PersistentIdentifierGenerator.TABLE, table.getName() );
if ( id.getColumnSpan() == 1 ) {
parameters.put( PersistentIdentifierGenerator.PK, id.getColumns().get(0).getName() );
}
// YUCK! but cannot think of a clean way to do this given the string-config based scheme
parameters.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, buildingContext.getObjectNameNormalizer() );
parameters.put( IdentifierGenerator.GENERATOR_NAME, generatorName );
if ( !generatorName.isEmpty() ) {
//we have a named generator
final IdentifierGeneratorDefinition definition = makeIdentifierGeneratorDefinition(
generatorName,
property,
localGenerators,
buildingContext
);
if ( definition == null ) {
throw new AnnotationException( "No id generator was declared with the name '" + generatorName
+ "' specified by '@GeneratedValue'"
+ " (define a named generator using '@SequenceGenerator', '@TableGenerator', or '@GenericGenerator')" );
}
//This is quite vague in the spec but a generator could override the generator choice
final String identifierGeneratorStrategy = definition.getStrategy();
//yuk! this is a hack not to override 'AUTO' even if generator is set
final boolean avoidOverriding = identifierGeneratorStrategy.equals( "identity" )
|| identifierGeneratorStrategy.equals( "seqhilo" );
if ( generatorType == null || !avoidOverriding ) {
id.setIdentifierGeneratorStrategy( identifierGeneratorStrategy );
if ( identifierGeneratorStrategy.equals( "assigned" ) ) {
id.setNullValue( "undefined" );
}
}
//checkIfMatchingGenerator(definition, generatorType, generatorName);
parameters.putAll( definition.getParameters() );
}
if ( DEFAULT_ID_GEN_STRATEGY.equals( generatorType ) ) {
id.setNullValue( "undefined" );
}
id.setIdentifierGeneratorParameters( parameters );
}
/**
* apply an id generator to a SimpleValue
*/
public static void makeIdGenerator(
SimpleValue id,
XProperty idXProperty,
String generatorType,
String generatorName,
MetadataBuildingContext buildingContext,
IdentifierGeneratorDefinition foreignKGeneratorDefinition) {
Map<String, IdentifierGeneratorDefinition> localIdentifiers = null;
if ( foreignKGeneratorDefinition != null ) {
localIdentifiers = new HashMap<>();
localIdentifiers.put( foreignKGeneratorDefinition.getName(), foreignKGeneratorDefinition );
}
makeIdGenerator( id, idXProperty, generatorType, generatorName, buildingContext, localIdentifiers );
}
private static IdentifierGeneratorDefinition makeIdentifierGeneratorDefinition(
String name,
XProperty idXProperty,
Map<String, IdentifierGeneratorDefinition> localGenerators,
MetadataBuildingContext buildingContext) {
if ( localGenerators != null ) {
final IdentifierGeneratorDefinition result = localGenerators.get( name );
if ( result != null ) {
return result;
}
}
final IdentifierGeneratorDefinition globalDefinition =
buildingContext.getMetadataCollector().getIdentifierGenerator( name );
if ( globalDefinition != null ) {
return globalDefinition;
}
log.debugf( "Could not resolve explicit IdentifierGeneratorDefinition - using implicit interpretation (%s)", name );
final GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class );
if ( generatedValue == null ) {
// this should really never happen, but it's easy to protect against it...
return new IdentifierGeneratorDefinition( DEFAULT_ID_GEN_STRATEGY, DEFAULT_ID_GEN_STRATEGY );
}
return IdentifierGeneratorDefinition.createImplicit(
name,
buildingContext
.getBootstrapContext()
.getReflectionManager()
.toClass( idXProperty.getType() ),
generatedValue.generator(),
buildingContext.getBuildingOptions().getIdGenerationTypeInterpreter(),
interpretGenerationType( generatedValue )
);
}
private static GenerationType interpretGenerationType(GeneratedValue generatedValueAnn) {
return generatedValueAnn.strategy() == null ? GenerationType.AUTO : generatedValueAnn.strategy();
}
public static Any buildAnyValue( public static Any buildAnyValue(
jakarta.persistence.Column discriminatorColumn, jakarta.persistence.Column discriminatorColumn,
Formula discriminatorFormula, Formula discriminatorFormula,
@ -946,11 +814,8 @@ public class BinderHelper {
final AnyDiscriminatorValues valuesAnn = property.getAnnotation( AnyDiscriminatorValues.class ); final AnyDiscriminatorValues valuesAnn = property.getAnnotation( AnyDiscriminatorValues.class );
if ( valuesAnn != null ) { if ( valuesAnn != null ) {
final AnyDiscriminatorValue[] valueAnns = valuesAnn.value(); for ( AnyDiscriminatorValue discriminatorValue : valuesAnn.value() ) {
if ( valueAnns != null && valueAnns.length > 0 ) { consumer.accept( discriminatorValue );
for ( AnyDiscriminatorValue ann : valueAnns ) {
consumer.accept(ann);
}
} }
} }
} }
@ -1007,9 +872,9 @@ public class BinderHelper {
public static Map<String,String> toAliasTableMap(SqlFragmentAlias[] aliases){ public static Map<String,String> toAliasTableMap(SqlFragmentAlias[] aliases){
final Map<String,String> ret = new HashMap<>(); final Map<String,String> ret = new HashMap<>();
for ( SqlFragmentAlias aliase : aliases ) { for ( SqlFragmentAlias alias : aliases ) {
if ( isNotEmpty( aliase.table() ) ) { if ( isNotEmpty( alias.table() ) ) {
ret.put( aliase.alias(), aliase.table() ); ret.put( alias.alias(), alias.table() );
} }
} }
return ret; return ret;
@ -1017,9 +882,9 @@ public class BinderHelper {
public static Map<String,String> toAliasEntityMap(SqlFragmentAlias[] aliases){ public static Map<String,String> toAliasEntityMap(SqlFragmentAlias[] aliases){
final Map<String,String> result = new HashMap<>(); final Map<String,String> result = new HashMap<>();
for ( SqlFragmentAlias aliase : aliases ) { for ( SqlFragmentAlias alias : aliases ) {
if ( aliase.entity() != void.class ) { if ( alias.entity() != void.class ) {
result.put( aliase.alias(), aliase.entity().getName() ); result.put( alias.alias(), alias.entity().getName() );
} }
} }
return result; return result;
@ -1070,8 +935,9 @@ public class BinderHelper {
type.getDeclaredMethod("before").invoke(annotation); type.getDeclaredMethod("before").invoke(annotation);
final DialectOverride.Version sameOrAfter = (DialectOverride.Version) final DialectOverride.Version sameOrAfter = (DialectOverride.Version)
type.getDeclaredMethod("sameOrAfter").invoke(annotation); type.getDeclaredMethod("sameOrAfter").invoke(annotation);
if ( dialect.getVersion().isBefore( before.major(), before.minor() ) DatabaseVersion version = dialect.getVersion();
&& dialect.getVersion().isSameOrAfter( sameOrAfter.major(), sameOrAfter.minor() ) ) { if ( version.isBefore( before.major(), before.minor() )
&& version.isSameOrAfter( sameOrAfter.major(), sameOrAfter.minor() ) ) {
//noinspection unchecked //noinspection unchecked
return (T) type.getDeclaredMethod("override").invoke(annotation); return (T) type.getDeclaredMethod("override").invoke(annotation);
} }
@ -1097,7 +963,7 @@ public class BinderHelper {
} }
private static CascadeType convertCascadeType(jakarta.persistence.CascadeType cascade) { private static CascadeType convertCascadeType(jakarta.persistence.CascadeType cascade) {
switch (cascade) { switch ( cascade ) {
case ALL: case ALL:
return CascadeType.ALL; return CascadeType.ALL;
case PERSIST: case PERSIST:
@ -1117,7 +983,7 @@ public class BinderHelper {
private static EnumSet<CascadeType> convertToHibernateCascadeType(jakarta.persistence.CascadeType[] ejbCascades) { private static EnumSet<CascadeType> convertToHibernateCascadeType(jakarta.persistence.CascadeType[] ejbCascades) {
final EnumSet<CascadeType> cascadeTypes = EnumSet.noneOf( CascadeType.class ); final EnumSet<CascadeType> cascadeTypes = EnumSet.noneOf( CascadeType.class );
if ( ejbCascades != null && ejbCascades.length > 0 ) { if ( ejbCascades != null ) {
for ( jakarta.persistence.CascadeType cascade: ejbCascades ) { for ( jakarta.persistence.CascadeType cascade: ejbCascades ) {
cascadeTypes.add( convertCascadeType( cascade ) ); cascadeTypes.add( convertCascadeType( cascade ) );
} }
@ -1185,4 +1051,16 @@ public class BinderHelper {
return cascade.length() > 0 ? cascade.substring( 1 ) : "none"; return cascade.length() > 0 ? cascade.substring( 1 ) : "none";
} }
static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) {
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
}
static boolean isCompositeId(XClass entityClass, XProperty idProperty) {
return entityClass.isAnnotationPresent( Embeddable.class )
|| idProperty.isAnnotationPresent( EmbeddedId.class );
}
public static boolean isDefault(XClass clazz, MetadataBuildingContext context) {
return context.getBootstrapContext().getReflectionManager().equals( clazz, void.class );
}
} }

View File

@ -79,12 +79,10 @@ import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrati
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData; import org.hibernate.boot.spi.PropertyData;
import org.hibernate.boot.spi.SecondPass; import org.hibernate.boot.spi.SecondPass;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.Any; import org.hibernate.mapping.Any;
import org.hibernate.mapping.Backref; import org.hibernate.mapping.Backref;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
@ -135,6 +133,8 @@ import jakarta.persistence.OrderColumn;
import jakarta.persistence.UniqueConstraint; import jakarta.persistence.UniqueConstraint;
import static jakarta.persistence.AccessType.PROPERTY; import static jakarta.persistence.AccessType.PROPERTY;
import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT;
import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT;
import static jakarta.persistence.FetchType.EAGER; import static jakarta.persistence.FetchType.EAGER;
import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.FetchType.LAZY;
import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE;
@ -145,27 +145,30 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnsFrom
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation;
import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix;
import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinTableJoinColumns; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinTableJoinColumns;
import static org.hibernate.boot.model.internal.AnnotationBinder.fillComponent;
import static org.hibernate.boot.model.internal.BinderHelper.buildAnyValue; import static org.hibernate.boot.model.internal.BinderHelper.buildAnyValue;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference; import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference;
import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy;
import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode;
import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation;
import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.getPath;
import static org.hibernate.boot.model.internal.BinderHelper.isDefault;
import static org.hibernate.boot.model.internal.BinderHelper.isPrimitive; import static org.hibernate.boot.model.internal.BinderHelper.isPrimitive;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.cfg.AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty; import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean;
/** /**
* Base class for binding different types of collections to mapping model objects * Base class for stateful binders responsible for producing mapping model objects of type {@link Collection}.
* of type {@link Collection}.
* *
* @author inger * @author inger
* @author Emmanuel Bernard * @author Emmanuel Bernard
@ -337,7 +340,7 @@ public abstract class CollectionBinder {
} }
if ( property.isAnnotationPresent( CollectionId.class ) ) { //do not compute the generators unless necessary if ( property.isAnnotationPresent( CollectionId.class ) ) { //do not compute the generators unless necessary
final HashMap<String, IdentifierGeneratorDefinition> localGenerators = new HashMap<>(classGenerators); final HashMap<String, IdentifierGeneratorDefinition> localGenerators = new HashMap<>(classGenerators);
localGenerators.putAll( AnnotationBinder.buildGenerators( property, context ) ); localGenerators.putAll( buildGenerators( property, context ) );
collectionBinder.setLocalGenerators( localGenerators ); collectionBinder.setLocalGenerators( localGenerators );
} }
@ -1497,7 +1500,7 @@ public abstract class CollectionBinder {
} }
XClass getElementType() { XClass getElementType() {
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) { if ( isDefault( targetEntity, buildingContext ) ) {
if ( collectionElementType != null ) { if ( collectionElementType != null ) {
return collectionElementType; return collectionElementType;
} }
@ -1759,12 +1762,12 @@ public abstract class CollectionBinder {
} }
private boolean useEntityWhereClauseForCollections() { private boolean useEntityWhereClauseForCollections() {
return ConfigurationHelper.getBoolean( return getBoolean(
AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS,
buildingContext buildingContext
.getBuildingOptions() .getBuildingOptions()
.getServiceRegistry() .getServiceRegistry()
.getService(ConfigurationService.class) .getService( ConfigurationService.class )
.getSettings(), .getSettings(),
true true
); );
@ -1891,13 +1894,9 @@ public abstract class CollectionBinder {
return orderByFragment; return orderByFragment;
} }
private DependantValue buildCollectionKey( private DependantValue buildCollectionKey(AnnotatedJoinColumns joinColumns, OnDeleteAction onDeleteAction) {
Collection collection,
AnnotatedJoinColumns joinColumns, final boolean noConstraintByDefault = buildingContext.getBuildingOptions().isNoConstraintByDefault();
OnDeleteAction onDeleteAction,
boolean noConstraintByDefault,
XProperty property,
PropertyHolder propertyHolder) {
// give a chance to override the referenced property name // give a chance to override the referenced property name
// has to do that here because the referencedProperty creation happens in a FKSecondPass for ManyToOne yuk! // has to do that here because the referencedProperty creation happens in a FKSecondPass for ManyToOne yuk!
@ -1928,8 +1927,8 @@ public abstract class CollectionBinder {
final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class ); final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class );
if ( collectionTableAnn != null ) { if ( collectionTableAnn != null ) {
final ForeignKey foreignKey = collectionTableAnn.foreignKey(); final ForeignKey foreignKey = collectionTableAnn.foreignKey();
if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT if ( foreignKey.value() == NO_CONSTRAINT
|| foreignKey.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { || foreignKey.value() == PROVIDER_DEFAULT && noConstraintByDefault ) {
key.disableForeignKey(); key.disableForeignKey();
} }
else { else {
@ -1957,12 +1956,12 @@ public abstract class CollectionBinder {
foreignKeyName = joinColumnAnn.foreignKey().name(); foreignKeyName = joinColumnAnn.foreignKey().name();
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition(); foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
} }
if ( foreignKeyValue != ConstraintMode.NO_CONSTRAINT ) { if ( foreignKeyValue != NO_CONSTRAINT ) {
foreignKeyValue = joinColumnAnn.foreignKey().value(); foreignKeyValue = joinColumnAnn.foreignKey().value();
} }
} }
if ( foreignKeyValue == ConstraintMode.NO_CONSTRAINT if ( foreignKeyValue == NO_CONSTRAINT
|| foreignKeyValue == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { || foreignKeyValue == PROVIDER_DEFAULT && noConstraintByDefault ) {
key.disableForeignKey(); key.disableForeignKey();
} }
else { else {
@ -2003,8 +2002,8 @@ public abstract class CollectionBinder {
private static void handleForeignKeyConstraint(boolean noConstraintByDefault, DependantValue key, ForeignKey foreignKey) { private static void handleForeignKeyConstraint(boolean noConstraintByDefault, DependantValue key, ForeignKey foreignKey) {
final ConstraintMode constraintMode = foreignKey.value(); final ConstraintMode constraintMode = foreignKey.value();
if ( constraintMode == ConstraintMode.NO_CONSTRAINT if ( constraintMode == NO_CONSTRAINT
|| constraintMode == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault) { || constraintMode == PROVIDER_DEFAULT && noConstraintByDefault) {
key.disableForeignKey(); key.disableForeignKey();
} }
else { else {
@ -2195,7 +2194,7 @@ public abstract class CollectionBinder {
CollectionPropertyHolder holder, CollectionPropertyHolder holder,
Class<? extends CompositeUserType<?>> compositeUserType) { Class<? extends CompositeUserType<?>> compositeUserType) {
//TODO be smart with isNullable //TODO be smart with isNullable
final Component component = fillComponent( final Component component = fillEmbeddable(
holder, holder,
getSpecialMembers( elementClass ), getSpecialMembers( elementClass ),
accessType( property, collection.getOwner() ), accessType( property, collection.getOwner() ),
@ -2303,7 +2302,7 @@ public abstract class CollectionBinder {
element.setReferencedEntityName( elementType.getName() ); element.setReferencedEntityName( elementType.getName() );
//element.setFetchMode( fetchMode ); //element.setFetchMode( fetchMode );
//element.setLazy( fetchMode != FetchMode.JOIN ); //element.setLazy( fetchMode != FetchMode.JOIN );
//make the second join non lazy //make the second join non-lazy
element.setFetchMode( FetchMode.JOIN ); element.setFetchMode( FetchMode.JOIN );
element.setLazy( false ); element.setLazy( false );
element.setNotFoundAction( notFoundAction ); element.setNotFoundAction( notFoundAction );
@ -2328,8 +2327,8 @@ public abstract class CollectionBinder {
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition(); foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
} }
} }
if ( joinTableAnn.inverseForeignKey().value() == ConstraintMode.NO_CONSTRAINT if ( joinTableAnn.inverseForeignKey().value() == NO_CONSTRAINT
|| joinTableAnn.inverseForeignKey().value() == ConstraintMode.PROVIDER_DEFAULT || joinTableAnn.inverseForeignKey().value() == PROVIDER_DEFAULT
&& buildingContext.getBuildingOptions().isNoConstraintByDefault() ) { && buildingContext.getBuildingOptions().isNoConstraintByDefault() ) {
element.disableForeignKey(); element.disableForeignKey();
} }
@ -2570,7 +2569,7 @@ public abstract class CollectionBinder {
AnnotatedJoinColumns joinColumns, AnnotatedJoinColumns joinColumns,
OnDeleteAction onDeleteAction) { OnDeleteAction onDeleteAction) {
if ( !isUnownedCollection()) { if ( !isUnownedCollection() ) {
createSyntheticPropertyReference( createSyntheticPropertyReference(
joinColumns, joinColumns,
collection.getOwner(), collection.getOwner(),
@ -2582,19 +2581,11 @@ public abstract class CollectionBinder {
); );
} }
final DependantValue key = buildCollectionKey(
collection,
joinColumns,
onDeleteAction,
buildingContext.getBuildingOptions().isNoConstraintByDefault(),
property,
propertyHolder
);
if ( property.isAnnotationPresent( ElementCollection.class ) ) { if ( property.isAnnotationPresent( ElementCollection.class ) ) {
joinColumns.setElementCollection( true ); joinColumns.setElementCollection( true );
} }
final DependantValue key = buildCollectionKey( joinColumns, onDeleteAction );
TableBinder.bindForeignKey( TableBinder.bindForeignKey(
collection.getOwner(), collection.getOwner(),
targetEntity, targetEntity,

View File

@ -0,0 +1,610 @@
/*
* 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.boot.model.internal;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Instantiator;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.internal.PropertyAccessStrategyCompositeUserTypeImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
import static org.hibernate.boot.model.internal.GeneratorBinder.generatorType;
import static org.hibernate.boot.model.internal.BinderHelper.getPath;
import static org.hibernate.boot.model.internal.BinderHelper.getPropertyOverriddenByMapperOrMapsId;
import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath;
import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation;
import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.internal.CoreLogging.messageLogger;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
/**
* A binder responsible for interpreting {@link Embeddable} classes and producing
* instances of the mapping model object {@link Component}.
*/
public class EmbeddableBinder {
private static final CoreMessageLogger LOG = messageLogger( EmbeddableBinder.class );
static PropertyBinder createCompositeBinder(
PropertyHolder propertyHolder,
PropertyData inferredData,
EntityBinder entityBinder,
boolean isIdentifierMapper,
boolean isComponentEmbedded,
MetadataBuildingContext context,
Map<XClass, InheritanceState> inheritanceStatePerClass,
XProperty property,
AnnotatedColumns columns,
XClass returnedClass,
PropertyBinder propertyBinder,
boolean isOverridden,
Class<? extends CompositeUserType<?>> compositeUserType) {
final String referencedEntityName;
final String propertyName;
final AnnotatedJoinColumns actualColumns;
if ( isOverridden ) {
// careful: not always a @MapsId property, sometimes it's from an @IdClass
final PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId(
propertyBinder.isId(),
propertyHolder,
property.getName(),
context
);
referencedEntityName = mapsIdProperty.getClassOrElementName();
propertyName = mapsIdProperty.getPropertyName();
final AnnotatedJoinColumns parent = new AnnotatedJoinColumns();
parent.setBuildingContext( context );
parent.setPropertyHolder( propertyHolder );
parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
//TODO: resetting the parent here looks like a dangerous thing to do
// should we be cloning them first (the legacy code did not)
for ( AnnotatedColumn column : columns.getColumns() ) {
column.setParent( parent );
}
actualColumns = parent;
}
else {
referencedEntityName = null;
propertyName = null;
actualColumns = null;
}
return bindEmbeddable(
inferredData,
propertyHolder,
entityBinder.getPropertyAccessor( property ),
entityBinder,
isIdentifierMapper,
context,
isComponentEmbedded,
propertyBinder.isId(),
inheritanceStatePerClass,
referencedEntityName,
propertyName,
determineCustomInstantiator( property, returnedClass, context ),
compositeUserType,
actualColumns,
columns
);
}
static boolean isEmbedded(XProperty property, XClass returnedClass) {
return property.isAnnotationPresent( Embedded.class )
|| property.isAnnotationPresent( EmbeddedId.class )
|| returnedClass.isAnnotationPresent( Embeddable.class );
}
private static PropertyBinder bindEmbeddable(
PropertyData inferredData,
PropertyHolder propertyHolder,
AccessType propertyAccessor,
EntityBinder entityBinder,
boolean isIdentifierMapper,
MetadataBuildingContext context,
boolean isComponentEmbedded,
boolean isId, //is an identifier
Map<XClass, InheritanceState> inheritanceStatePerClass,
String referencedEntityName, //is a component who is overridden by a @MapsId
String propertyName,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
AnnotatedJoinColumns columns,
AnnotatedColumns annotatedColumns) {
final Component component;
if ( referencedEntityName != null ) {
component = createEmbeddable(
propertyHolder,
inferredData,
isComponentEmbedded,
isIdentifierMapper,
customInstantiatorImpl,
context
);
context.getMetadataCollector().addSecondPass( new CopyIdentifierComponentSecondPass(
component,
referencedEntityName,
propertyName,
columns,
context
) );
}
else {
component = fillEmbeddable(
propertyHolder,
inferredData,
propertyAccessor,
!isId,
entityBinder,
isComponentEmbedded,
isIdentifierMapper,
false,
customInstantiatorImpl,
compositeUserTypeClass,
annotatedColumns,
context,
inheritanceStatePerClass
);
}
if ( isId ) {
component.setKey( true );
checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component );
}
final PropertyBinder binder = new PropertyBinder();
binder.setDeclaringClass( inferredData.getDeclaringClass() );
binder.setName( inferredData.getPropertyName() );
binder.setValue( component );
binder.setProperty( inferredData.getProperty() );
binder.setAccessType( inferredData.getDefaultAccess() );
binder.setEmbedded( isComponentEmbedded );
binder.setHolder( propertyHolder );
binder.setId( isId );
binder.setEntityBinder( entityBinder );
binder.setInheritanceStatePerClass( inheritanceStatePerClass );
binder.setBuildingContext( context );
binder.makePropertyAndBind();
return binder;
}
private static void checkEmbeddedId(
PropertyData inferredData,
PropertyHolder propertyHolder,
String referencedEntityName,
Component component) {
if ( propertyHolder.getPersistentClass().getIdentifier() != null ) {
throw new AnnotationException(
"Embeddable class '" + component.getComponentClassName()
+ "' may not have a property annotated '@Id' since it is used by '"
+ getPath(propertyHolder, inferredData)
+ "' as an '@EmbeddedId'"
);
}
if ( referencedEntityName == null && component.getPropertySpan() == 0 ) {
throw new AnnotationException(
"Embeddable class '" + component.getComponentClassName()
+ "' may not be used as an '@EmbeddedId' by '"
+ getPath(propertyHolder, inferredData)
+ "' because it has no properties"
);
}
}
static Component fillEmbeddable(
PropertyHolder propertyHolder,
PropertyData inferredData,
AccessType propertyAccessor,
boolean isNullable,
EntityBinder entityBinder,
boolean isComponentEmbedded,
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
AnnotatedColumns columns,
MetadataBuildingContext context,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
return fillEmbeddable(
propertyHolder,
inferredData,
null,
propertyAccessor,
isNullable,
entityBinder,
isComponentEmbedded,
isIdentifierMapper,
inSecondPass,
customInstantiatorImpl,
compositeUserTypeClass,
columns,
context,
inheritanceStatePerClass
);
}
static Component fillEmbeddable(
PropertyHolder propertyHolder,
PropertyData inferredData,
PropertyData baseInferredData, //base inferred data correspond to the entity reproducing inferredData's properties (ie IdClass)
AccessType propertyAccessor,
boolean isNullable,
EntityBinder entityBinder,
boolean isComponentEmbedded,
boolean isIdentifierMapper,
boolean inSecondPass,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
AnnotatedColumns columns,
MetadataBuildingContext context,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
// inSecondPass can only be used to apply right away the second pass of a composite-element
// Because it's a value type, there is no bidirectional association, hence second pass
// ordering does not matter
final Component component = createEmbeddable(
propertyHolder,
inferredData,
isComponentEmbedded,
isIdentifierMapper,
customInstantiatorImpl,
context
);
final String subpath = getPath( propertyHolder, inferredData );
LOG.tracev( "Binding component with path: {0}", subpath );
final PropertyHolder subholder = buildPropertyHolder(
component,
subpath,
inferredData,
propertyHolder,
context
);
// propertyHolder here is the owner of the component property.
// Tell it we are about to start the component...
propertyHolder.startingProperty( inferredData.getProperty() );
final CompositeUserType<?> compositeUserType;
final XClass returnedClassOrElement;
if ( compositeUserTypeClass == null ) {
compositeUserType = null;
returnedClassOrElement = inferredData.getClassOrElement();
}
else {
compositeUserType = compositeUserType( compositeUserTypeClass, context );
component.setTypeName( compositeUserTypeClass.getName() );
returnedClassOrElement = context.getBootstrapContext().getReflectionManager()
.toXClass( compositeUserType.embeddable() );
}
final XClass annotatedClass = inferredData.getPropertyClass();
final List<PropertyData> classElements =
collectClassElements( propertyAccessor, context, returnedClassOrElement, annotatedClass );
final List<PropertyData> baseClassElements =
collectBaseClassElements( baseInferredData, propertyAccessor, context, annotatedClass );
if ( baseClassElements != null
//useful to avoid breaking pre JPA 2 mappings
&& !hasAnnotationsOnIdClass( annotatedClass ) ) {
processIdClassElememts( propertyHolder, baseInferredData, classElements, baseClassElements );
}
for ( PropertyData propertyAnnotatedElement : classElements ) {
processElementAnnotations(
subholder,
isNullable ? Nullability.NO_CONSTRAINT : Nullability.FORCED_NOT_NULL,
propertyAnnotatedElement,
new HashMap<>(),
entityBinder,
isIdentifierMapper,
isComponentEmbedded,
inSecondPass,
context,
inheritanceStatePerClass
);
final XProperty property = propertyAnnotatedElement.getProperty();
if ( isGeneratedId( property ) ) {
processGeneratedId( context, component, property );
}
}
if ( compositeUserType != null ) {
processCompositeUserType( component, compositeUserType );
}
AggregateComponentBinder.processAggregate(
component,
propertyHolder,
inferredData,
returnedClassOrElement,
columns,
context
);
return component;
}
private static CompositeUserType<?> compositeUserType(
Class<? extends CompositeUserType<?>> compositeUserTypeClass,
MetadataBuildingContext context) {
return context.getBootstrapContext().getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( compositeUserTypeClass )
.getBeanInstance();
}
private static List<PropertyData> collectClassElements(
AccessType propertyAccessor,
MetadataBuildingContext context,
XClass returnedClassOrElement,
XClass annotatedClass) {
final List<PropertyData> classElements = new ArrayList<>();
//embeddable elements can have type defs
final PropertyContainer container =
new PropertyContainer( returnedClassOrElement, annotatedClass, propertyAccessor );
addElementsOfClass( classElements, container, context);
//add elements of the embeddable's mapped superclasses
XClass superClass = annotatedClass.getSuperclass();
while ( superClass != null && superClass.isAnnotationPresent( MappedSuperclass.class ) ) {
//FIXME: proper support of type variables incl var resolved at upper levels
final PropertyContainer superContainer =
new PropertyContainer( superClass, annotatedClass, propertyAccessor );
addElementsOfClass( classElements, superContainer, context );
superClass = superClass.getSuperclass();
}
return classElements;
}
private static List<PropertyData> collectBaseClassElements(
PropertyData baseInferredData,
AccessType propertyAccessor,
MetadataBuildingContext context,
XClass annotatedClass) {
if ( baseInferredData != null ) {
final List<PropertyData> baseClassElements = new ArrayList<>();
// iterate from base returned class up hierarchy to handle cases where the @Id attributes
// might be spread across the subclasses and super classes.
XClass baseReturnedClassOrElement = baseInferredData.getClassOrElement();
while ( !Object.class.getName().equals( baseReturnedClassOrElement.getName() ) ) {
final PropertyContainer container =
new PropertyContainer( baseReturnedClassOrElement, annotatedClass, propertyAccessor );
addElementsOfClass( baseClassElements, container, context );
baseReturnedClassOrElement = baseReturnedClassOrElement.getSuperclass();
}
return baseClassElements;
}
else {
return null;
}
}
private static boolean isGeneratedId(XProperty property) {
return property.isAnnotationPresent( GeneratedValue.class )
&& property.isAnnotationPresent( Id.class );
}
private static void processCompositeUserType(Component component, CompositeUserType<?> compositeUserType) {
component.sortProperties();
final List<String> sortedPropertyNames = new ArrayList<>( component.getPropertySpan() );
final List<Type> sortedPropertyTypes = new ArrayList<>( component.getPropertySpan() );
final PropertyAccessStrategy strategy = new PropertyAccessStrategyCompositeUserTypeImpl(
compositeUserType,
sortedPropertyNames,
sortedPropertyTypes
);
for ( Property property : component.getProperties() ) {
sortedPropertyNames.add( property.getName() );
sortedPropertyTypes.add(
PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess(
compositeUserType.embeddable(),
property.getName(),
false
).getGetter().getReturnType()
);
property.setPropertyAccessStrategy( strategy );
}
}
private static boolean hasAnnotationsOnIdClass(XClass idClass) {
for ( XProperty property : idClass.getDeclaredProperties( XClass.ACCESS_FIELD ) ) {
if ( hasTriggeringAnnotation( property ) ) {
return true;
}
}
for ( XMethod method : idClass.getDeclaredMethods() ) {
if ( hasTriggeringAnnotation( method ) ) {
return true;
}
}
return false;
}
private static boolean hasTriggeringAnnotation(XAnnotatedElement property) {
return property.isAnnotationPresent(Column.class)
|| property.isAnnotationPresent(OneToMany.class)
|| property.isAnnotationPresent(ManyToOne.class)
|| property.isAnnotationPresent(Id.class)
|| property.isAnnotationPresent(GeneratedValue.class)
|| property.isAnnotationPresent(OneToOne.class)
|| property.isAnnotationPresent(ManyToMany.class);
}
private static void processGeneratedId(MetadataBuildingContext context, Component component, XProperty property) {
final GeneratedValue generatedValue = property.getAnnotation( GeneratedValue.class );
final String generatorType = generatedValue != null
? generatorType( generatedValue, property.getType(), context )
: DEFAULT_ID_GEN_STRATEGY;
final String generator = generatedValue != null ? generatedValue.generator() : "";
if ( isGlobalGeneratorNameGlobal( context ) ) {
buildGenerators( property, context );
context.getMetadataCollector().addSecondPass( new IdGeneratorResolverSecondPass(
(SimpleValue) component.getProperty( property.getName() ).getValue(),
property,
generatorType,
generator,
context
) );
// handleTypeDescriptorRegistrations( property, context );
// bindEmbeddableInstantiatorRegistrations( property, context );
// bindCompositeUserTypeRegistrations( property, context );
// handleConverterRegistrations( property, context );
}
else {
makeIdGenerator(
(SimpleValue) component.getProperty( property.getName() ).getValue(),
property,
generatorType,
generator,
context,
new HashMap<>( buildGenerators( property, context ) )
);
}
}
private static void processIdClassElememts(
PropertyHolder propertyHolder,
PropertyData baseInferredData,
List<PropertyData> classElements,
List<PropertyData> baseClassElements) {
final Map<String, PropertyData> baseClassElementsByName = new HashMap<>();
for ( PropertyData element : baseClassElements ) {
baseClassElementsByName.put( element.getPropertyName(), element );
}
for ( int i = 0; i < classElements.size(); i++ ) {
final PropertyData idClassPropertyData = classElements.get( i );
final PropertyData entityPropertyData =
baseClassElementsByName.get( idClassPropertyData.getPropertyName() );
if ( propertyHolder.isInIdClass() ) {
if ( entityPropertyData == null ) {
throw new AnnotationException(
"Property '" + getPath(propertyHolder, idClassPropertyData )
+ "' belongs to an '@IdClass' but has no matching property in entity class '"
+ baseInferredData.getPropertyClass().getName()
+ "' (every property of the '@IdClass' must have a corresponding persistent property in the '@Entity' class)"
);
}
if ( hasToOneAnnotation( entityPropertyData.getProperty() )
&& !entityPropertyData.getClassOrElement().equals( idClassPropertyData.getClassOrElement() ) ) {
//don't replace here as we need to use the actual original return type
//the annotation overriding will be dealt with by a mechanism similar to @MapsId
continue;
}
}
classElements.set( i, entityPropertyData ); //this works since they are in the same order
}
}
static Component createEmbeddable(
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean isComponentEmbedded,
boolean isIdentifierMapper,
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
MetadataBuildingContext context) {
final Component component = new Component( context, propertyHolder.getPersistentClass() );
component.setEmbedded( isComponentEmbedded );
//yuk
component.setTable( propertyHolder.getTable() );
//FIXME shouldn't identifier mapper use getClassOrElementName? Need to be checked.
if ( isIdentifierMapper
|| isComponentEmbedded && inferredData.getPropertyName() == null ) {
component.setComponentClassName( component.getOwner().getClassName() );
}
else {
component.setComponentClassName( inferredData.getClassOrElementName() );
}
component.setCustomInstantiator( customInstantiatorImpl );
final Constructor<?> constructor = resolveInstantiator( inferredData.getClassOrElement(), context );
if ( constructor != null ) {
component.setInstantiator( constructor, constructor.getAnnotation( Instantiator.class ).value() );
}
return component;
}
private static Constructor<?> resolveInstantiator(XClass embeddableClass, MetadataBuildingContext buildingContext) {
if ( embeddableClass != null ) {
final Constructor<?>[] declaredConstructors = buildingContext.getBootstrapContext().getReflectionManager()
.toClass( embeddableClass )
.getDeclaredConstructors();
Constructor<?> constructor = null;
for ( Constructor<?> declaredConstructor : declaredConstructors ) {
if ( declaredConstructor.isAnnotationPresent( Instantiator.class ) ) {
if ( constructor != null ) {
throw new AnnotationException( "Multiple constructors of '" + embeddableClass.getName()
+ "' are annotated '@Instantiator' but only one constructor can be the canonical constructor" );
}
constructor = declaredConstructor;
}
}
return constructor;
}
return null;
}
private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( property.isAnnotationPresent( EmbeddedId.class ) ) {
// we don't allow custom instantiators for composite ids
return null;
}
final org.hibernate.annotations.EmbeddableInstantiator propertyAnnotation =
property.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( propertyAnnotation != null ) {
return propertyAnnotation.value();
}
final org.hibernate.annotations.EmbeddableInstantiator classAnnotation =
returnedClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( classAnnotation != null ) {
return classAnnotation.value();
}
final Class<?> embeddableClass = context.getBootstrapContext().getReflectionManager().toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredEmbeddableInstantiator( embeddableClass );
}
return null;
}
}

View File

@ -17,6 +17,27 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import jakarta.persistence.Access;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Cacheable;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.IdClass;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedEntityGraphs;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.SecondaryTables;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.MappingException; import org.hibernate.MappingException;
@ -99,37 +120,20 @@ import org.hibernate.spi.NavigablePath;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import jakarta.persistence.Access;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Cacheable;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.IdClass;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedEntityGraphs;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.SecondaryTables;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.UniqueConstraint;
import static org.hibernate.boot.model.internal.AnnotatedClassType.MAPPED_SUPERCLASS; import static org.hibernate.boot.model.internal.AnnotatedClassType.MAPPED_SUPERCLASS;
import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn; import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn;
import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildInheritanceJoinColumn; import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildInheritanceJoinColumn;
import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull; import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull;
import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation;
import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation;
import static org.hibernate.boot.model.internal.BinderHelper.makeIdGenerator; import static org.hibernate.boot.model.internal.BinderHelper.isDefault;
import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity; import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isEmpty;
@ -142,7 +146,8 @@ import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
/** /**
* Stateful holder and processor for binding information about an {@link Entity} class. * Stateful binder responsible for interpreting information about an {@link Entity} class
* and producing a {@link PersistentClass} mapping model object.
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
@ -400,7 +405,7 @@ public class EntityBinder {
XClass compositeClass, XClass compositeClass,
PropertyData baseInferredData, PropertyData baseInferredData,
AccessType propertyAccessor) { AccessType propertyAccessor) {
final Component mapper = AnnotationBinder.fillComponent( final Component mapper = fillEmbeddable(
propertyHolder, propertyHolder,
new PropertyPreloadedData( new PropertyPreloadedData(
propertyAccessor, propertyAccessor,
@ -434,6 +439,22 @@ public class EntityBinder {
return mapper; return mapper;
} }
private static PropertyData getUniqueIdPropertyFromBaseClass(
PropertyData inferredData,
PropertyData baseInferredData,
AccessType propertyAccessor,
MetadataBuildingContext context) {
final List<PropertyData> baseClassElements = new ArrayList<>();
final PropertyContainer propContainer = new PropertyContainer(
baseInferredData.getClassOrElement(),
inferredData.getPropertyClass(),
propertyAccessor
);
addElementsOfClass( baseClassElements, propContainer, context );
//Id properties are on top and there is only one
return baseClassElements.get( 0 );
}
private static boolean isIdClassPkOfTheAssociatedEntity( private static boolean isIdClassPkOfTheAssociatedEntity(
ElementsToProcess elementsToProcess, ElementsToProcess elementsToProcess,
XClass compositeClass, XClass compositeClass,
@ -443,7 +464,7 @@ public class EntityBinder {
Map<XClass, InheritanceState> inheritanceStates, Map<XClass, InheritanceState> inheritanceStates,
MetadataBuildingContext context) { MetadataBuildingContext context) {
if ( elementsToProcess.getIdPropertyCount() == 1 ) { if ( elementsToProcess.getIdPropertyCount() == 1 ) {
final PropertyData idPropertyOnBaseClass = AnnotationBinder.getUniqueIdPropertyFromBaseClass( final PropertyData idPropertyOnBaseClass = getUniqueIdPropertyFromBaseClass(
inferredData, inferredData,
baseInferredData, baseInferredData,
propertyAccessor, propertyAccessor,
@ -488,7 +509,7 @@ public class EntityBinder {
+ "' is a subclass in an entity inheritance hierarchy and may not redefine the identifier of the root entity" ); + "' is a subclass in an entity inheritance hierarchy and may not redefine the identifier of the root entity" );
} }
final RootClass rootClass = (RootClass) persistentClass; final RootClass rootClass = (RootClass) persistentClass;
final Component id = AnnotationBinder.fillComponent( final Component id = fillEmbeddable(
propertyHolder, propertyHolder,
inferredData, inferredData,
baseInferredData, baseInferredData,
@ -783,7 +804,7 @@ public class EntityBinder {
final DiscriminatorFormula discriminatorFormula = final DiscriminatorFormula discriminatorFormula =
getOverridableAnnotation( annotatedClass, DiscriminatorFormula.class, context ); getOverridableAnnotation( annotatedClass, DiscriminatorFormula.class, context );
if ( !inheritanceState.hasParents() ) { if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) {
return buildDiscriminatorColumn( discriminatorColumn, discriminatorFormula, context ); return buildDiscriminatorColumn( discriminatorColumn, discriminatorFormula, context );
} }
else { else {
@ -812,7 +833,7 @@ public class EntityBinder {
} }
final DiscriminatorColumn discriminatorColumn = annotatedClass.getAnnotation( DiscriminatorColumn.class ); final DiscriminatorColumn discriminatorColumn = annotatedClass.getAnnotation( DiscriminatorColumn.class );
if ( !inheritanceState.hasParents() ) { if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) {
return useDiscriminatorColumnForJoined( discriminatorColumn ) return useDiscriminatorColumnForJoined( discriminatorColumn )
? buildDiscriminatorColumn( discriminatorColumn, null, context ) ? buildDiscriminatorColumn( discriminatorColumn, null, context )
: null; : null;
@ -876,7 +897,7 @@ public class EntityBinder {
boolean subclassAndSingleTableStrategy = boolean subclassAndSingleTableStrategy =
inheritanceState.getType() == InheritanceType.SINGLE_TABLE inheritanceState.getType() == InheritanceType.SINGLE_TABLE
&& inheritanceState.hasParents(); && inheritanceState.hasParents();
AnnotationBinder.processElementAnnotations( processElementAnnotations(
propertyHolder, propertyHolder,
subclassAndSingleTableStrategy subclassAndSingleTableStrategy
? Nullability.FORCED_NULL ? Nullability.FORCED_NULL
@ -1355,7 +1376,7 @@ public class EntityBinder {
} }
else { else {
final ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager(); final ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager();
proxyClass = AnnotationBinder.isDefault( reflectionManager.toXClass(proxy.proxyClass() ), context ) proxyClass = isDefault( reflectionManager.toXClass(proxy.proxyClass() ), context )
? annotatedClass ? annotatedClass
: reflectionManager.toXClass(proxy.proxyClass()); : reflectionManager.toXClass(proxy.proxyClass());
} }

View File

@ -0,0 +1,509 @@
/*
* 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.boot.model.internal;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.SequenceGenerators;
import jakarta.persistence.TableGenerator;
import jakarta.persistence.TableGenerators;
import jakarta.persistence.Version;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.IdGeneratorStrategyInterpreter;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.relational.ExportableProducer;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.generator.AnnotationBasedGenerator;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
import org.hibernate.internal.CoreLogging;
import org.hibernate.mapping.GeneratorCreator;
import org.hibernate.mapping.IdentifierGeneratorCreator;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.jboss.logging.Logger;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.util.HashMap;
import java.util.Map;
import static org.hibernate.boot.model.internal.BinderHelper.isCompositeId;
import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
public class GeneratorBinder {
private static final Logger LOG = CoreLogging.logger( BinderHelper.class );
/**
* Apply an id generation strategy and parameters to the
* given {@link SimpleValue} which represents an identifier.
*/
public static void makeIdGenerator(
SimpleValue id,
XProperty property,
String generatorType,
String generatorName,
MetadataBuildingContext buildingContext,
Map<String, IdentifierGeneratorDefinition> localGenerators) {
LOG.debugf( "#makeIdGenerator(%s, %s, %s, %s, ...)", id, property, generatorType, generatorName );
final Table table = id.getTable();
table.setIdentifierValue( id );
//generator settings
id.setIdentifierGeneratorStrategy( generatorType );
final Map<String,Object> parameters = new HashMap<>();
//always settable
parameters.put( PersistentIdentifierGenerator.TABLE, table.getName() );
if ( id.getColumnSpan() == 1 ) {
parameters.put( PersistentIdentifierGenerator.PK, id.getColumns().get(0).getName() );
}
// YUCK! but cannot think of a clean way to do this given the string-config based scheme
parameters.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, buildingContext.getObjectNameNormalizer() );
parameters.put( IdentifierGenerator.GENERATOR_NAME, generatorName );
if ( !generatorName.isEmpty() ) {
//we have a named generator
final IdentifierGeneratorDefinition definition = makeIdentifierGeneratorDefinition(
generatorName,
property,
localGenerators,
buildingContext
);
if ( definition == null ) {
throw new AnnotationException( "No id generator was declared with the name '" + generatorName
+ "' specified by '@GeneratedValue'"
+ " (define a named generator using '@SequenceGenerator', '@TableGenerator', or '@GenericGenerator')" );
}
//This is quite vague in the spec but a generator could override the generator choice
final String identifierGeneratorStrategy = definition.getStrategy();
//yuk! this is a hack not to override 'AUTO' even if generator is set
final boolean avoidOverriding = identifierGeneratorStrategy.equals( "identity" )
|| identifierGeneratorStrategy.equals( "seqhilo" );
if ( generatorType == null || !avoidOverriding ) {
id.setIdentifierGeneratorStrategy( identifierGeneratorStrategy );
if ( identifierGeneratorStrategy.equals( "assigned" ) ) {
id.setNullValue( "undefined" );
}
}
//checkIfMatchingGenerator(definition, generatorType, generatorName);
parameters.putAll( definition.getParameters() );
}
if ( DEFAULT_ID_GEN_STRATEGY.equals( generatorType ) ) {
id.setNullValue( "undefined" );
}
id.setIdentifierGeneratorParameters( parameters );
}
/**
* apply an id generator to a SimpleValue
*/
public static void makeIdGenerator(
SimpleValue id,
XProperty idXProperty,
String generatorType,
String generatorName,
MetadataBuildingContext buildingContext,
IdentifierGeneratorDefinition foreignKGeneratorDefinition) {
Map<String, IdentifierGeneratorDefinition> localIdentifiers = null;
if ( foreignKGeneratorDefinition != null ) {
localIdentifiers = new HashMap<>();
localIdentifiers.put( foreignKGeneratorDefinition.getName(), foreignKGeneratorDefinition );
}
makeIdGenerator( id, idXProperty, generatorType, generatorName, buildingContext, localIdentifiers );
}
private static IdentifierGeneratorDefinition makeIdentifierGeneratorDefinition(
String name,
XProperty idXProperty,
Map<String, IdentifierGeneratorDefinition> localGenerators,
MetadataBuildingContext buildingContext) {
if ( localGenerators != null ) {
final IdentifierGeneratorDefinition result = localGenerators.get( name );
if ( result != null ) {
return result;
}
}
final IdentifierGeneratorDefinition globalDefinition =
buildingContext.getMetadataCollector().getIdentifierGenerator( name );
if ( globalDefinition != null ) {
return globalDefinition;
}
LOG.debugf( "Could not resolve explicit IdentifierGeneratorDefinition - using implicit interpretation (%s)", name );
final GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class );
if ( generatedValue == null ) {
// this should really never happen, but it's easy to protect against it...
return new IdentifierGeneratorDefinition( DEFAULT_ID_GEN_STRATEGY, DEFAULT_ID_GEN_STRATEGY );
}
return IdentifierGeneratorDefinition.createImplicit(
name,
buildingContext
.getBootstrapContext()
.getReflectionManager()
.toClass( idXProperty.getType() ),
generatedValue.generator(),
buildingContext.getBuildingOptions().getIdGenerationTypeInterpreter(),
interpretGenerationType( generatedValue )
);
}
private static GenerationType interpretGenerationType(GeneratedValue generatedValueAnn) {
return generatedValueAnn.strategy() == null ? GenerationType.AUTO : generatedValueAnn.strategy();
}
public static Map<String, IdentifierGeneratorDefinition> buildGenerators(
XAnnotatedElement annotatedElement,
MetadataBuildingContext context) {
final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
final Map<String, IdentifierGeneratorDefinition> generators = new HashMap<>();
final TableGenerators tableGenerators = annotatedElement.getAnnotation( TableGenerators.class );
if ( tableGenerators != null ) {
for ( TableGenerator tableGenerator : tableGenerators.value() ) {
IdentifierGeneratorDefinition idGenerator = buildIdGenerator( tableGenerator, context );
generators.put(
idGenerator.getName(),
idGenerator
);
metadataCollector.addIdentifierGenerator( idGenerator );
}
}
final SequenceGenerators sequenceGenerators = annotatedElement.getAnnotation( SequenceGenerators.class );
if ( sequenceGenerators != null ) {
for ( SequenceGenerator sequenceGenerator : sequenceGenerators.value() ) {
IdentifierGeneratorDefinition idGenerator = buildIdGenerator( sequenceGenerator, context );
generators.put(
idGenerator.getName(),
idGenerator
);
metadataCollector.addIdentifierGenerator( idGenerator );
}
}
final TableGenerator tableGenerator = annotatedElement.getAnnotation( TableGenerator.class );
if ( tableGenerator != null ) {
IdentifierGeneratorDefinition idGenerator = buildIdGenerator( tableGenerator, context );
generators.put( idGenerator.getName(), idGenerator );
metadataCollector.addIdentifierGenerator( idGenerator );
}
final SequenceGenerator sequenceGenerator = annotatedElement.getAnnotation( SequenceGenerator.class );
if ( sequenceGenerator != null ) {
IdentifierGeneratorDefinition idGenerator = buildIdGenerator( sequenceGenerator, context );
generators.put( idGenerator.getName(), idGenerator );
metadataCollector.addIdentifierGenerator( idGenerator );
}
final GenericGenerator genericGenerator = annotatedElement.getAnnotation( GenericGenerator.class );
if ( genericGenerator != null ) {
final IdentifierGeneratorDefinition idGenerator = buildIdGenerator( genericGenerator, context );
generators.put( idGenerator.getName(), idGenerator );
metadataCollector.addIdentifierGenerator( idGenerator );
}
return generators;
}
static String generatorType(
MetadataBuildingContext context,
XClass entityXClass,
boolean isComponent,
GeneratedValue generatedValue) {
if ( isComponent ) {
//a component must not have any generator
return DEFAULT_ID_GEN_STRATEGY;
}
else {
return generatedValue == null ? DEFAULT_ID_GEN_STRATEGY : generatorType( generatedValue, entityXClass, context );
}
}
static String generatorType(GeneratedValue generatedValue, final XClass javaClass, MetadataBuildingContext context) {
return context.getBuildingOptions().getIdGenerationTypeInterpreter()
.determineGeneratorName(
generatedValue.strategy(),
new IdGeneratorStrategyInterpreter.GeneratorNameDeterminationContext() {
Class<?> javaType = null;
@Override
public Class<?> getIdType() {
if ( javaType == null ) {
javaType = context.getBootstrapContext().getReflectionManager().toClass( javaClass );
}
return javaType;
}
@Override
public String getGeneratedValueGeneratorName() {
return generatedValue.generator();
}
}
);
}
static IdentifierGeneratorDefinition buildIdGenerator(
Annotation generatorAnnotation,
MetadataBuildingContext context) {
if ( generatorAnnotation == null ) {
return null;
}
final IdentifierGeneratorDefinition.Builder definitionBuilder = new IdentifierGeneratorDefinition.Builder();
if ( generatorAnnotation instanceof TableGenerator ) {
context.getBuildingOptions().getIdGenerationTypeInterpreter().interpretTableGenerator(
(TableGenerator) generatorAnnotation,
definitionBuilder
);
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Add table generator with name: {0}", definitionBuilder.getName() );
}
}
else if ( generatorAnnotation instanceof SequenceGenerator ) {
context.getBuildingOptions().getIdGenerationTypeInterpreter().interpretSequenceGenerator(
(SequenceGenerator) generatorAnnotation,
definitionBuilder
);
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Add sequence generator with name: {0}", definitionBuilder.getName() );
}
}
else if ( generatorAnnotation instanceof GenericGenerator ) {
final GenericGenerator genericGenerator = (GenericGenerator) generatorAnnotation;
definitionBuilder.setName( genericGenerator.name() );
final String strategy = genericGenerator.type().equals(Generator.class)
? genericGenerator.strategy()
: genericGenerator.type().getName();
definitionBuilder.setStrategy( strategy );
for ( Parameter parameter : genericGenerator.parameters() ) {
definitionBuilder.addParam( parameter.name(), parameter.value() );
}
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Add generic generator with name: {0}", definitionBuilder.getName() );
}
}
else {
throw new AssertionFailure( "Unknown Generator annotation: " + generatorAnnotation );
}
return definitionBuilder.build();
}
private static void checkGeneratorClass(Class<? extends Generator> generatorClass) {
if ( !BeforeExecutionGenerator.class.isAssignableFrom( generatorClass )
&& !OnExecutionGenerator.class.isAssignableFrom( generatorClass ) ) {
throw new MappingException("Generator class '" + generatorClass.getName()
+ "' must implement either 'BeforeExecutionGenerator' or 'OnExecutionGenerator'");
}
}
private static void checkGeneratorInterfaces(Class<? extends Generator> generatorClass) {
// we don't yet support the additional "fancy" operations of
// IdentifierGenerator with regular generators, though this
// would be extremely easy to add if anyone asks for it
if ( IdentifierGenerator.class.isAssignableFrom( generatorClass ) ) {
throw new AnnotationException("Generator class '" + generatorClass.getName()
+ "' implements 'IdentifierGenerator' and may not be used with '@ValueGenerationType'");
}
if ( ExportableProducer.class.isAssignableFrom( generatorClass ) ) {
throw new AnnotationException("Generator class '" + generatorClass.getName()
+ "' implements 'ExportableProducer' and may not be used with '@ValueGenerationType'");
}
}
/**
* In case the given annotation is a value generator annotation, the corresponding value generation strategy to be
* applied to the given property is returned, {@code null} otherwise.
* Instantiates the given generator annotation type, initializing it with the given instance of the corresponding
* generator annotation and the property's type.
*/
static GeneratorCreator generatorCreator(XProperty property, Annotation annotation) {
final Member member = HCANNHelper.getUnderlyingMember( property );
final Class<? extends Annotation> annotationType = annotation.annotationType();
final ValueGenerationType generatorAnnotation = annotationType.getAnnotation( ValueGenerationType.class );
if ( generatorAnnotation == null ) {
return null;
}
final Class<? extends Generator> generatorClass = generatorAnnotation.generatedBy();
checkGeneratorClass( generatorClass );
checkGeneratorInterfaces( generatorClass );
return creationContext -> {
final Generator generator =
instantiateGenerator(
annotation,
member,
annotationType,
creationContext,
GeneratorCreationContext.class,
generatorClass
);
callInitialize( annotation, member, creationContext, generator );
checkVersionGenerationAlways( property, generator );
return generator;
};
}
static IdentifierGeneratorCreator identifierGeneratorCreator(XProperty idProperty, Annotation annotation) {
final Member member = HCANNHelper.getUnderlyingMember( idProperty );
final Class<? extends Annotation> annotationType = annotation.annotationType();
final IdGeneratorType idGeneratorType = annotationType.getAnnotation( IdGeneratorType.class );
assert idGeneratorType != null;
return creationContext -> {
final Class<? extends Generator> generatorClass = idGeneratorType.value();
checkGeneratorClass( generatorClass );
final Generator generator =
instantiateGenerator(
annotation,
member,
annotationType,
creationContext,
CustomIdGeneratorCreationContext.class,
generatorClass
);
callInitialize( annotation, member, creationContext, generator );
checkIdGeneratorTiming( annotationType, generator );
return generator;
};
}
private static <C, G extends Generator> G instantiateGenerator(
Annotation annotation,
Member member,
Class<? extends Annotation> annotationType,
C creationContext,
Class<C> contextClass,
Class<? extends G> generatorClass) {
try {
try {
return generatorClass
.getConstructor( annotationType, Member.class, contextClass )
.newInstance( annotation, member, creationContext);
}
catch (NoSuchMethodException ignore) {
try {
return generatorClass
.getConstructor( annotationType )
.newInstance( annotation );
}
catch (NoSuchMethodException i) {
return generatorClass.newInstance();
}
}
}
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new HibernateException(
"Could not instantiate generator of type '" + generatorClass.getName() + "'",
e
);
}
}
private static <A extends Annotation> void callInitialize(
A annotation,
Member member,
GeneratorCreationContext creationContext,
Generator generator) {
if ( generator instanceof AnnotationBasedGenerator) {
// This will cause a CCE in case the generation type doesn't match the annotation type; As this would be
// a programming error of the generation type developer and thus should show up during testing, we don't
// check this explicitly; If required, this could be done e.g. using ClassMate
@SuppressWarnings("unchecked")
final AnnotationBasedGenerator<A> generation = (AnnotationBasedGenerator<A>) generator;
generation.initialize( annotation, member, creationContext );
}
}
private static void checkVersionGenerationAlways(XProperty property, Generator generator) {
if ( property.isAnnotationPresent(Version.class) ) {
if ( !generator.generatesOnInsert() ) {
throw new AnnotationException("Property '" + property.getName()
+ "' is annotated '@Version' but has a 'Generator' which does not generate on inserts"
);
}
if ( !generator.generatesOnUpdate() ) {
throw new AnnotationException("Property '" + property.getName()
+ "' is annotated '@Version' but has a 'Generator' which does not generate on updates"
);
}
}
}
private static void checkIdGeneratorTiming(Class<? extends Annotation> annotationType, Generator generator) {
if ( !generator.generatesOnInsert() ) {
throw new MappingException( "Annotation '" + annotationType
+ "' is annotated 'IdGeneratorType' but the given 'Generator' does not generate on inserts");
}
if ( generator.generatesOnUpdate() ) {
throw new MappingException( "Annotation '" + annotationType
+ "' is annotated 'IdGeneratorType' but the given 'Generator' generates on updates (it must generate only on inserts)");
}
}
static void createIdGenerator(
SimpleValue idValue,
Map<String, IdentifierGeneratorDefinition> classGenerators,
MetadataBuildingContext context,
XClass entityClass,
XProperty idProperty) {
//manage composite related metadata
//guess if its a component and find id data access (property, field etc)
final GeneratedValue generatedValue = idProperty.getAnnotation( GeneratedValue.class );
final String generatorType = generatorType( context, entityClass, isCompositeId( entityClass, idProperty ), generatedValue );
final String generatorName = generatedValue == null ? "" : generatedValue.generator();
if ( isGlobalGeneratorNameGlobal( context ) ) {
buildGenerators( idProperty, context );
context.getMetadataCollector().addSecondPass( new IdGeneratorResolverSecondPass(
idValue,
idProperty,
generatorType,
generatorName,
context
) );
}
else {
//clone classGenerator and override with local values
final Map<String, IdentifierGeneratorDefinition> generators = new HashMap<>( classGenerators );
generators.putAll( buildGenerators( idProperty, context ) );
makeIdGenerator( idValue, idProperty, generatorType, generatorName, context, generators );
}
}
static IdentifierGeneratorDefinition createForeignGenerator(PropertyData mapsIdProperty) {
final IdentifierGeneratorDefinition.Builder foreignGeneratorBuilder =
new IdentifierGeneratorDefinition.Builder();
foreignGeneratorBuilder.setName( "Hibernate-local--foreign generator" );
foreignGeneratorBuilder.setStrategy( "foreign" );
foreignGeneratorBuilder.addParam( "property", mapsIdProperty.getPropertyName() );
return foreignGeneratorBuilder.build();
}
}

View File

@ -26,7 +26,7 @@ import org.hibernate.usertype.UserCollectionType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import static org.hibernate.boot.model.internal.BinderHelper.makeIdGenerator; import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
/** /**
* A {@link CollectionBinder} for {@link org.hibernate.collection.spi.PersistentIdentifierBag id bags} * A {@link CollectionBinder} for {@link org.hibernate.collection.spi.PersistentIdentifierBag id bags}

View File

@ -16,7 +16,7 @@ import org.hibernate.boot.spi.SecondPass;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SimpleValue;
import static org.hibernate.boot.model.internal.BinderHelper.makeIdGenerator; import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
/** /**
* @author Andrea Boriero * @author Andrea Boriero

View File

@ -28,6 +28,10 @@ import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType; import jakarta.persistence.InheritanceType;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import static jakarta.persistence.InheritanceType.SINGLE_TABLE;
import static jakarta.persistence.InheritanceType.TABLE_PER_CLASS;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
/** /**
* Some extra data to the inheritance position of a class. * Some extra data to the inheritance position of a class.
* *
@ -73,16 +77,16 @@ public class InheritanceState {
setType( inhAnn == null ? null : inhAnn.strategy() ); setType( inhAnn == null ? null : inhAnn.strategy() );
} }
else { else {
setType( inhAnn == null ? InheritanceType.SINGLE_TABLE : inhAnn.strategy() ); setType( inhAnn == null ? SINGLE_TABLE : inhAnn.strategy() );
} }
} }
public boolean hasTable() { public boolean hasTable() {
return !hasParents() || !InheritanceType.SINGLE_TABLE.equals( getType() ); return !hasParents() || !SINGLE_TABLE.equals( getType() );
} }
public boolean hasDenormalizedTable() { public boolean hasDenormalizedTable() {
return hasParents() && InheritanceType.TABLE_PER_CLASS.equals( getType() ); return hasParents() && TABLE_PER_CLASS.equals( getType() );
} }
public static InheritanceState getInheritanceStateOfSuperEntity(XClass clazz, Map<XClass, InheritanceState> states) { public static InheritanceState getInheritanceStateOfSuperEntity(XClass clazz, Map<XClass, InheritanceState> states) {
@ -167,7 +171,7 @@ public class InheritanceState {
return clazz; return clazz;
} }
else { else {
InheritanceState state = InheritanceState.getSuperclassInheritanceState( clazz, inheritanceStatePerClass ); final InheritanceState state = getSuperclassInheritanceState( clazz, inheritanceStatePerClass );
if ( state != null ) { if ( state != null ) {
return state.getClassWithIdClass( true ); return state.getClassWithIdClass( true );
} }
@ -210,7 +214,7 @@ public class InheritanceState {
accessType = determineDefaultAccessType(); accessType = determineDefaultAccessType();
ArrayList<PropertyData> elements = new ArrayList<>(); final ArrayList<PropertyData> elements = new ArrayList<>();
int idPropertyCount = 0; int idPropertyCount = 0;
for ( XClass classToProcessForMappedSuperclass : classesToProcessForMappedSuperclass ) { for ( XClass classToProcessForMappedSuperclass : classesToProcessForMappedSuperclass ) {
@ -219,7 +223,7 @@ public class InheritanceState {
clazz, clazz,
accessType accessType
); );
int currentIdPropertyCount = AnnotationBinder.addElementsOfClass( int currentIdPropertyCount = addElementsOfClass(
elements, elements,
propertyContainer, propertyContainer,
buildingContext buildingContext
@ -238,7 +242,7 @@ public class InheritanceState {
} }
private AccessType determineDefaultAccessType() { private AccessType determineDefaultAccessType() {
for (XClass xclass = clazz; xclass != null; xclass = xclass.getSuperclass()) { for ( XClass xclass = clazz; xclass != null; xclass = xclass.getSuperclass() ) {
if ( ( xclass.getSuperclass() == null || Object.class.getName().equals( xclass.getSuperclass().getName() ) ) if ( ( xclass.getSuperclass() == null || Object.class.getName().equals( xclass.getSuperclass().getName() ) )
&& ( xclass.isAnnotationPresent( Entity.class ) || xclass.isAnnotationPresent( MappedSuperclass.class ) ) && ( xclass.isAnnotationPresent( Entity.class ) || xclass.isAnnotationPresent( MappedSuperclass.class ) )
&& xclass.isAnnotationPresent( Access.class ) ) { && xclass.isAnnotationPresent( Access.class ) ) {
@ -248,7 +252,9 @@ public class InheritanceState {
// Guess from identifier. // Guess from identifier.
// FIX: Shouldn't this be determined by the first attribute (i.e., field or property) with annotations, but without an // FIX: Shouldn't this be determined by the first attribute (i.e., field or property) with annotations, but without an
// explicit Access annotation, according to JPA 2.0 spec 2.3.1: Default Access Type? // explicit Access annotation, according to JPA 2.0 spec 2.3.1: Default Access Type?
for (XClass xclass = clazz; xclass != null && !Object.class.getName().equals(xclass.getName()); xclass = xclass.getSuperclass()) { for ( XClass xclass = clazz;
xclass != null && !Object.class.getName().equals( xclass.getName() );
xclass = xclass.getSuperclass() ) {
if ( xclass.isAnnotationPresent( Entity.class ) || xclass.isAnnotationPresent( MappedSuperclass.class ) ) { if ( xclass.isAnnotationPresent( Entity.class ) || xclass.isAnnotationPresent( MappedSuperclass.class ) ) {
for ( XProperty prop : xclass.getDeclaredProperties( AccessType.PROPERTY.getType() ) ) { for ( XProperty prop : xclass.getDeclaredProperties( AccessType.PROPERTY.getType() ) ) {
final boolean isEmbeddedId = prop.isAnnotationPresent( EmbeddedId.class ); final boolean isEmbeddedId = prop.isAnnotationPresent( EmbeddedId.class );

View File

@ -53,6 +53,7 @@ import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE;
import static org.hibernate.boot.model.internal.AnnotatedClassType.NONE; import static org.hibernate.boot.model.internal.AnnotatedClassType.NONE;
import static org.hibernate.boot.model.internal.BinderHelper.findPropertyByName; import static org.hibernate.boot.model.internal.BinderHelper.findPropertyByName;
import static org.hibernate.boot.model.internal.BinderHelper.isPrimitive; import static org.hibernate.boot.model.internal.BinderHelper.isPrimitive;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
@ -365,7 +366,7 @@ public class MapBinder extends CollectionBinder {
CollectionPropertyHolder holder, CollectionPropertyHolder holder,
AccessType accessType, AccessType accessType,
Class<? extends CompositeUserType<?>> compositeUserType) { Class<? extends CompositeUserType<?>> compositeUserType) {
getMap().setIndex( AnnotationBinder.fillComponent( getMap().setIndex( fillEmbeddable(
holder, holder,
propertyPreloadedData( keyClass ), propertyPreloadedData( keyClass ),
accessType, accessType,

View File

@ -50,7 +50,10 @@ import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.setOf; import static org.hibernate.internal.util.collections.CollectionHelper.setOf;
/** /**
* Query binder * Responsible for reading named queries defined in annotations and registering
* {@link org.hibernate.boot.query.NamedQueryDefinition} objects.
*
* @implNote This class is stateless, unlike most of the other "binders".
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */

View File

@ -51,7 +51,7 @@ import static org.hibernate.internal.util.StringHelper.unquote;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
/** /**
* Table related operations * Stateful binder responsible for producing instances of {@link Table}.
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */

View File

@ -0,0 +1,75 @@
/*
* 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.boot.model.internal;
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;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.internal.OffsetDateTimeCompositeUserType;
import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import static org.hibernate.TimeZoneStorageStrategy.COLUMN;
import static org.hibernate.dialect.TimeZoneSupport.NATIVE;
public class TimeZoneStorageHelper {
private static final String OFFSET_DATETIME_CLASS = OffsetDateTime.class.getName();
private static final String ZONED_DATETIME_CLASS = ZonedDateTime.class.getName();
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() == 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() != NATIVE;
default:
return false;
}
}
}
}

View File

@ -46,16 +46,26 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.PrimaryKeyJoinColumn; import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns; import jakarta.persistence.PrimaryKeyJoinColumns;
import static org.hibernate.boot.model.internal.AnnotationBinder.matchIgnoreNotFoundWithFetchType; import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT;
import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT;
import static jakarta.persistence.FetchType.EAGER;
import static jakarta.persistence.FetchType.LAZY;
import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy;
import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode;
import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.getPath;
import static org.hibernate.boot.model.internal.BinderHelper.isDefault;
import static org.hibernate.internal.CoreLogging.messageLogger; import static org.hibernate.internal.CoreLogging.messageLogger;
import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
/** /**
* Responsible for interpreting {@link ManyToOne} and {@link OneToOne} associations
* and producing mapping model objects of type {@link org.hibernate.mapping.ManyToOne}
* and {@link org.hibernate.mapping.OneToOne}.
*
* @implNote This class is stateless, unlike most of the other "binders".
*
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
public class ToOneBinder { public class ToOneBinder {
@ -243,7 +253,8 @@ public class ToOneBinder {
PropertyData inferredData, PropertyData inferredData,
boolean isIdentifierMapper, boolean isIdentifierMapper,
PropertyBinder propertyBinder, PropertyBinder propertyBinder,
org.hibernate.mapping.ManyToOne value, XProperty property, org.hibernate.mapping.ManyToOne value,
XProperty property,
boolean hasSpecjManyToOne, boolean hasSpecjManyToOne,
String propertyName) { String propertyName) {
@ -265,7 +276,7 @@ public class ToOneBinder {
propertyBinder.setAccessType( inferredData.getDefaultAccess() ); propertyBinder.setAccessType( inferredData.getDefaultAccess() );
propertyBinder.setCascade( cascadeStrategy ); propertyBinder.setCascade( cascadeStrategy );
propertyBinder.setProperty( property ); propertyBinder.setProperty( property );
propertyBinder.setXToMany( true ); propertyBinder.setToMany( true );
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class ); final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class ); final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
@ -346,7 +357,7 @@ public class ToOneBinder {
// LazyToOne takes precedent // LazyToOne takes precedent
final LazyToOne lazy = property.getAnnotation( LazyToOne.class ); final LazyToOne lazy = property.getAnnotation( LazyToOne.class );
boolean eager = lazy.value() == LazyToOneOption.FALSE; boolean eager = lazy.value() == LazyToOneOption.FALSE;
if ( eager && fetchType == FetchType.LAZY ) { if ( eager && fetchType == LAZY ) {
// conflicts with non-default setting // conflicts with non-default setting
throw new AnnotationException("Association '" + getPath(propertyHolder, inferredData) throw new AnnotationException("Association '" + getPath(propertyHolder, inferredData)
+ "' is marked 'fetch=LAZY' and '@LazyToOne(FALSE)'"); + "' is marked 'fetch=LAZY' and '@LazyToOne(FALSE)'");
@ -354,7 +365,7 @@ public class ToOneBinder {
return eager; return eager;
} }
else { else {
return fetchType == FetchType.EAGER; return fetchType == EAGER;
} }
} }
@ -579,20 +590,20 @@ public class ToOneBinder {
} }
else { else {
ConstraintMode mode = joinColumns.value(); ConstraintMode mode = joinColumns.value();
return mode == ConstraintMode.NO_CONSTRAINT return mode == NO_CONSTRAINT
|| mode == ConstraintMode.PROVIDER_DEFAULT && context.getBuildingOptions().isNoConstraintByDefault(); || mode == PROVIDER_DEFAULT && context.getBuildingOptions().isNoConstraintByDefault();
} }
} }
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) { public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
return AnnotationBinder.isDefault( targetEntity, context ) return isDefault( targetEntity, context )
? propertyData.getClassOrElementName() ? propertyData.getClassOrElementName()
: targetEntity.getName(); : targetEntity.getName();
} }
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) { public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) {
final XClass targetEntity = getTargetEntity( propertyData, context ); final XClass targetEntity = getTargetEntity( propertyData, context );
return AnnotationBinder.isDefault( targetEntity, context ) return isDefault( targetEntity, context )
? propertyData.getClassOrElementName() ? propertyData.getClassOrElementName()
: targetEntity.getName(); : targetEntity.getName();
} }
@ -613,4 +624,14 @@ public class ToOneBinder {
} }
throw new AssertionFailure("Unexpected discovery of a targetEntity: " + property.getName() ); throw new AssertionFailure("Unexpected discovery of a targetEntity: " + property.getName() );
} }
private static void matchIgnoreNotFoundWithFetchType(
String entity,
String association,
NotFoundAction notFoundAction,
FetchType fetchType) {
if ( notFoundAction != null && fetchType == LAZY ) {
LOG.ignoreNotFoundWithFetchTypeLazy( entity, association );
}
}
} }

View File

@ -1730,8 +1730,8 @@ public interface CoreMessageLogger extends BasicLogger {
void usingJtaPlatform(String jtaPlatformClassName); void usingJtaPlatform(String jtaPlatformClassName);
@LogMessage(level = WARN) @LogMessage(level = WARN)
@Message(value = "`.%1$s.%2$s` uses both @NotFound and FetchType.LAZY. @ManyToOne and " + @Message(value = "'%1$s.%2$s' uses both @NotFound and FetchType.LAZY. @ManyToOne and " +
"@OneToOne associations mapped with `@NotFound` are forced to EAGER fetching.", id = 491) "@OneToOne associations mapped with @NotFound are forced to EAGER fetching.", id = 491)
void ignoreNotFoundWithFetchTypeLazy(String entity, String association); void ignoreNotFoundWithFetchTypeLazy(String entity, String association);
@LogMessage(level = INFO) @LogMessage(level = INFO)

View File

@ -13,7 +13,7 @@ import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.model.internal.AnnotationBinder; import org.hibernate.boot.model.internal.ToOneBinder;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
@ -44,10 +44,10 @@ public class JoinFormulaManyToOneNotIgnoreLazyFetchingTest extends BaseEntityMan
@Rule @Rule
public LoggerInspectionRule logInspection = new LoggerInspectionRule( public LoggerInspectionRule logInspection = new LoggerInspectionRule(
Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) Logger.getMessageLogger( CoreMessageLogger.class, ToOneBinder.class.getName() )
); );
private Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" ); private final Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" );
@Override @Override

View File

@ -13,7 +13,7 @@ import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.model.internal.AnnotationBinder; import org.hibernate.boot.model.internal.ToOneBinder;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
@ -44,10 +44,10 @@ public class JoinFormulaOneToOneNotIgnoreLazyFetchingTest extends BaseEntityMana
@Rule @Rule
public LoggerInspectionRule logInspection = new LoggerInspectionRule( public LoggerInspectionRule logInspection = new LoggerInspectionRule(
Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) Logger.getMessageLogger( CoreMessageLogger.class, ToOneBinder.class.getName() )
); );
private Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" ); private final Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" );
@Override @Override

View File

@ -26,7 +26,6 @@ import jakarta.persistence.metamodel.Type;
import org.hibernate.boot.Metadata; import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources; import org.hibernate.boot.MetadataSources;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.orm.test.legacy.I;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
@ -105,7 +104,6 @@ public class MetadataTest {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testBuildingMetamodelWithParameterizedCollection() { public void testBuildingMetamodelWithParameterizedCollection() {
Metadata metadata = new MetadataSources() Metadata metadata = new MetadataSources()
.addAnnotatedClass(WithGenericCollection.class) .addAnnotatedClass(WithGenericCollection.class)
@ -441,7 +439,4 @@ public class MetadataTest {
} }
} }
//todo test plural
} }