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.MappedSuperclass;
import static org.hibernate.boot.model.internal.AnnotationBinder.useColumnForTimeZoneStorage;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.useColumnForTimeZoneStorage;
/**
* @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.function.Function;
import jakarta.persistence.Basic;
import jakarta.persistence.FetchType;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
@ -96,6 +94,8 @@ import jakarta.persistence.Version;
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
/**
* A stateful binder responsible for creating instances of {@link BasicValue}.
*
* @author Steve Ebersole
* @author Emmanuel Bernard
*/
@ -107,26 +107,6 @@ public class BasicValueBinder implements JdbcTypeIndicators {
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 {
ATTRIBUTE( ValueMappingAccess.INSTANCE ),
ANY_DISCRIMINATOR( AnyDiscriminatorMappingAccess.INSTANCE ),

View File

@ -22,6 +22,8 @@ import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
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.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.dialect.DatabaseVersion;
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.BasicValue;
import org.hibernate.mapping.Collection;
@ -55,18 +54,13 @@ import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.SyntheticProperty;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.type.descriptor.java.JavaType;
import org.jboss.logging.Logger;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.ManyToOne;
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.isNotEmpty;
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.NOOP;
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.interpret;
@ -84,8 +77,6 @@ import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.
*/
public class BinderHelper {
private static final Logger log = CoreLogging.logger( BinderHelper.class );
private BinderHelper() {
}
@ -731,129 +722,6 @@ public class BinderHelper {
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(
jakarta.persistence.Column discriminatorColumn,
Formula discriminatorFormula,
@ -946,11 +814,8 @@ public class BinderHelper {
final AnyDiscriminatorValues valuesAnn = property.getAnnotation( AnyDiscriminatorValues.class );
if ( valuesAnn != null ) {
final AnyDiscriminatorValue[] valueAnns = valuesAnn.value();
if ( valueAnns != null && valueAnns.length > 0 ) {
for ( AnyDiscriminatorValue ann : valueAnns ) {
consumer.accept(ann);
}
for ( AnyDiscriminatorValue discriminatorValue : valuesAnn.value() ) {
consumer.accept( discriminatorValue );
}
}
}
@ -1007,9 +872,9 @@ public class BinderHelper {
public static Map<String,String> toAliasTableMap(SqlFragmentAlias[] aliases){
final Map<String,String> ret = new HashMap<>();
for ( SqlFragmentAlias aliase : aliases ) {
if ( isNotEmpty( aliase.table() ) ) {
ret.put( aliase.alias(), aliase.table() );
for ( SqlFragmentAlias alias : aliases ) {
if ( isNotEmpty( alias.table() ) ) {
ret.put( alias.alias(), alias.table() );
}
}
return ret;
@ -1017,9 +882,9 @@ public class BinderHelper {
public static Map<String,String> toAliasEntityMap(SqlFragmentAlias[] aliases){
final Map<String,String> result = new HashMap<>();
for ( SqlFragmentAlias aliase : aliases ) {
if ( aliase.entity() != void.class ) {
result.put( aliase.alias(), aliase.entity().getName() );
for ( SqlFragmentAlias alias : aliases ) {
if ( alias.entity() != void.class ) {
result.put( alias.alias(), alias.entity().getName() );
}
}
return result;
@ -1070,8 +935,9 @@ public class BinderHelper {
type.getDeclaredMethod("before").invoke(annotation);
final DialectOverride.Version sameOrAfter = (DialectOverride.Version)
type.getDeclaredMethod("sameOrAfter").invoke(annotation);
if ( dialect.getVersion().isBefore( before.major(), before.minor() )
&& dialect.getVersion().isSameOrAfter( sameOrAfter.major(), sameOrAfter.minor() ) ) {
DatabaseVersion version = dialect.getVersion();
if ( version.isBefore( before.major(), before.minor() )
&& version.isSameOrAfter( sameOrAfter.major(), sameOrAfter.minor() ) ) {
//noinspection unchecked
return (T) type.getDeclaredMethod("override").invoke(annotation);
}
@ -1117,7 +983,7 @@ public class BinderHelper {
private static EnumSet<CascadeType> convertToHibernateCascadeType(jakarta.persistence.CascadeType[] ejbCascades) {
final EnumSet<CascadeType> cascadeTypes = EnumSet.noneOf( CascadeType.class );
if ( ejbCascades != null && ejbCascades.length > 0 ) {
if ( ejbCascades != null ) {
for ( jakarta.persistence.CascadeType cascade: ejbCascades ) {
cascadeTypes.add( convertCascadeType( cascade ) );
}
@ -1185,4 +1051,16 @@ public class BinderHelper {
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.PropertyData;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.Backref;
import org.hibernate.mapping.Collection;
@ -135,6 +133,8 @@ import jakarta.persistence.OrderColumn;
import jakarta.persistence.UniqueConstraint;
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.LAZY;
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.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix;
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.GeneratorBinder.buildGenerators;
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.getFetchMode;
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.isDefault;
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.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
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.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
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
* of type {@link Collection}.
* Base class for stateful binders responsible for producing mapping model objects of type {@link Collection}.
*
* @author inger
* @author Emmanuel Bernard
@ -337,7 +340,7 @@ public abstract class CollectionBinder {
}
if ( property.isAnnotationPresent( CollectionId.class ) ) { //do not compute the generators unless necessary
final HashMap<String, IdentifierGeneratorDefinition> localGenerators = new HashMap<>(classGenerators);
localGenerators.putAll( AnnotationBinder.buildGenerators( property, context ) );
localGenerators.putAll( buildGenerators( property, context ) );
collectionBinder.setLocalGenerators( localGenerators );
}
@ -1497,7 +1500,7 @@ public abstract class CollectionBinder {
}
XClass getElementType() {
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) {
if ( isDefault( targetEntity, buildingContext ) ) {
if ( collectionElementType != null ) {
return collectionElementType;
}
@ -1759,8 +1762,8 @@ public abstract class CollectionBinder {
}
private boolean useEntityWhereClauseForCollections() {
return ConfigurationHelper.getBoolean(
AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS,
return getBoolean(
USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS,
buildingContext
.getBuildingOptions()
.getServiceRegistry()
@ -1891,13 +1894,9 @@ public abstract class CollectionBinder {
return orderByFragment;
}
private DependantValue buildCollectionKey(
Collection collection,
AnnotatedJoinColumns joinColumns,
OnDeleteAction onDeleteAction,
boolean noConstraintByDefault,
XProperty property,
PropertyHolder propertyHolder) {
private DependantValue buildCollectionKey(AnnotatedJoinColumns joinColumns, OnDeleteAction onDeleteAction) {
final boolean noConstraintByDefault = buildingContext.getBuildingOptions().isNoConstraintByDefault();
// 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!
@ -1928,8 +1927,8 @@ public abstract class CollectionBinder {
final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class );
if ( collectionTableAnn != null ) {
final ForeignKey foreignKey = collectionTableAnn.foreignKey();
if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT
|| foreignKey.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) {
if ( foreignKey.value() == NO_CONSTRAINT
|| foreignKey.value() == PROVIDER_DEFAULT && noConstraintByDefault ) {
key.disableForeignKey();
}
else {
@ -1957,12 +1956,12 @@ public abstract class CollectionBinder {
foreignKeyName = joinColumnAnn.foreignKey().name();
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
}
if ( foreignKeyValue != ConstraintMode.NO_CONSTRAINT ) {
if ( foreignKeyValue != NO_CONSTRAINT ) {
foreignKeyValue = joinColumnAnn.foreignKey().value();
}
}
if ( foreignKeyValue == ConstraintMode.NO_CONSTRAINT
|| foreignKeyValue == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) {
if ( foreignKeyValue == NO_CONSTRAINT
|| foreignKeyValue == PROVIDER_DEFAULT && noConstraintByDefault ) {
key.disableForeignKey();
}
else {
@ -2003,8 +2002,8 @@ public abstract class CollectionBinder {
private static void handleForeignKeyConstraint(boolean noConstraintByDefault, DependantValue key, ForeignKey foreignKey) {
final ConstraintMode constraintMode = foreignKey.value();
if ( constraintMode == ConstraintMode.NO_CONSTRAINT
|| constraintMode == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault) {
if ( constraintMode == NO_CONSTRAINT
|| constraintMode == PROVIDER_DEFAULT && noConstraintByDefault) {
key.disableForeignKey();
}
else {
@ -2195,7 +2194,7 @@ public abstract class CollectionBinder {
CollectionPropertyHolder holder,
Class<? extends CompositeUserType<?>> compositeUserType) {
//TODO be smart with isNullable
final Component component = fillComponent(
final Component component = fillEmbeddable(
holder,
getSpecialMembers( elementClass ),
accessType( property, collection.getOwner() ),
@ -2303,7 +2302,7 @@ public abstract class CollectionBinder {
element.setReferencedEntityName( elementType.getName() );
//element.setFetchMode( fetchMode );
//element.setLazy( fetchMode != FetchMode.JOIN );
//make the second join non lazy
//make the second join non-lazy
element.setFetchMode( FetchMode.JOIN );
element.setLazy( false );
element.setNotFoundAction( notFoundAction );
@ -2328,8 +2327,8 @@ public abstract class CollectionBinder {
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
}
}
if ( joinTableAnn.inverseForeignKey().value() == ConstraintMode.NO_CONSTRAINT
|| joinTableAnn.inverseForeignKey().value() == ConstraintMode.PROVIDER_DEFAULT
if ( joinTableAnn.inverseForeignKey().value() == NO_CONSTRAINT
|| joinTableAnn.inverseForeignKey().value() == PROVIDER_DEFAULT
&& buildingContext.getBuildingOptions().isNoConstraintByDefault() ) {
element.disableForeignKey();
}
@ -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 ) ) {
joinColumns.setElementCollection( true );
}
final DependantValue key = buildCollectionKey( joinColumns, onDeleteAction );
TableBinder.bindForeignKey(
collection.getOwner(),
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.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.AssertionFailure;
import org.hibernate.MappingException;
@ -99,37 +120,20 @@ import org.hibernate.spi.NavigablePath;
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.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn;
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.getOverridableAnnotation;
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.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.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
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
*/
@ -400,7 +405,7 @@ public class EntityBinder {
XClass compositeClass,
PropertyData baseInferredData,
AccessType propertyAccessor) {
final Component mapper = AnnotationBinder.fillComponent(
final Component mapper = fillEmbeddable(
propertyHolder,
new PropertyPreloadedData(
propertyAccessor,
@ -434,6 +439,22 @@ public class EntityBinder {
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(
ElementsToProcess elementsToProcess,
XClass compositeClass,
@ -443,7 +464,7 @@ public class EntityBinder {
Map<XClass, InheritanceState> inheritanceStates,
MetadataBuildingContext context) {
if ( elementsToProcess.getIdPropertyCount() == 1 ) {
final PropertyData idPropertyOnBaseClass = AnnotationBinder.getUniqueIdPropertyFromBaseClass(
final PropertyData idPropertyOnBaseClass = getUniqueIdPropertyFromBaseClass(
inferredData,
baseInferredData,
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" );
}
final RootClass rootClass = (RootClass) persistentClass;
final Component id = AnnotationBinder.fillComponent(
final Component id = fillEmbeddable(
propertyHolder,
inferredData,
baseInferredData,
@ -783,7 +804,7 @@ public class EntityBinder {
final DiscriminatorFormula discriminatorFormula =
getOverridableAnnotation( annotatedClass, DiscriminatorFormula.class, context );
if ( !inheritanceState.hasParents() ) {
if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) {
return buildDiscriminatorColumn( discriminatorColumn, discriminatorFormula, context );
}
else {
@ -812,7 +833,7 @@ public class EntityBinder {
}
final DiscriminatorColumn discriminatorColumn = annotatedClass.getAnnotation( DiscriminatorColumn.class );
if ( !inheritanceState.hasParents() ) {
if ( !inheritanceState.hasParents() || annotatedClass.isAnnotationPresent( Inheritance.class ) ) {
return useDiscriminatorColumnForJoined( discriminatorColumn )
? buildDiscriminatorColumn( discriminatorColumn, null, context )
: null;
@ -876,7 +897,7 @@ public class EntityBinder {
boolean subclassAndSingleTableStrategy =
inheritanceState.getType() == InheritanceType.SINGLE_TABLE
&& inheritanceState.hasParents();
AnnotationBinder.processElementAnnotations(
processElementAnnotations(
propertyHolder,
subclassAndSingleTableStrategy
? Nullability.FORCED_NULL
@ -1355,7 +1376,7 @@ public class EntityBinder {
}
else {
final ReflectionManager reflectionManager = context.getBootstrapContext().getReflectionManager();
proxyClass = AnnotationBinder.isDefault( reflectionManager.toXClass(proxy.proxyClass() ), context )
proxyClass = isDefault( reflectionManager.toXClass(proxy.proxyClass() ), context )
? annotatedClass
: 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 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}

View File

@ -16,7 +16,7 @@ import org.hibernate.boot.spi.SecondPass;
import org.hibernate.mapping.PersistentClass;
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

View File

@ -28,6 +28,10 @@ import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
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.
*
@ -73,16 +77,16 @@ public class InheritanceState {
setType( inhAnn == null ? null : inhAnn.strategy() );
}
else {
setType( inhAnn == null ? InheritanceType.SINGLE_TABLE : inhAnn.strategy() );
setType( inhAnn == null ? SINGLE_TABLE : inhAnn.strategy() );
}
}
public boolean hasTable() {
return !hasParents() || !InheritanceType.SINGLE_TABLE.equals( getType() );
return !hasParents() || !SINGLE_TABLE.equals( getType() );
}
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) {
@ -167,7 +171,7 @@ public class InheritanceState {
return clazz;
}
else {
InheritanceState state = InheritanceState.getSuperclassInheritanceState( clazz, inheritanceStatePerClass );
final InheritanceState state = getSuperclassInheritanceState( clazz, inheritanceStatePerClass );
if ( state != null ) {
return state.getClassWithIdClass( true );
}
@ -210,7 +214,7 @@ public class InheritanceState {
accessType = determineDefaultAccessType();
ArrayList<PropertyData> elements = new ArrayList<>();
final ArrayList<PropertyData> elements = new ArrayList<>();
int idPropertyCount = 0;
for ( XClass classToProcessForMappedSuperclass : classesToProcessForMappedSuperclass ) {
@ -219,7 +223,7 @@ public class InheritanceState {
clazz,
accessType
);
int currentIdPropertyCount = AnnotationBinder.addElementsOfClass(
int currentIdPropertyCount = addElementsOfClass(
elements,
propertyContainer,
buildingContext
@ -248,7 +252,9 @@ public class InheritanceState {
// Guess from identifier.
// 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?
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 ) ) {
for ( XProperty prop : xclass.getDeclaredProperties( AccessType.PROPERTY.getType() ) ) {
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.BinderHelper.findPropertyByName;
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.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.qualify;
@ -365,7 +366,7 @@ public class MapBinder extends CollectionBinder {
CollectionPropertyHolder holder,
AccessType accessType,
Class<? extends CompositeUserType<?>> compositeUserType) {
getMap().setIndex( AnnotationBinder.fillComponent(
getMap().setIndex( fillEmbeddable(
holder,
propertyPreloadedData( keyClass ),
accessType,

View File

@ -50,7 +50,10 @@ import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
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
*/

View File

@ -51,7 +51,7 @@ import static org.hibernate.internal.util.StringHelper.unquote;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
/**
* Table related operations
* Stateful binder responsible for producing instances of {@link Table}.
*
* @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.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.getFetchMode;
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.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
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
*/
public class ToOneBinder {
@ -243,7 +253,8 @@ public class ToOneBinder {
PropertyData inferredData,
boolean isIdentifierMapper,
PropertyBinder propertyBinder,
org.hibernate.mapping.ManyToOne value, XProperty property,
org.hibernate.mapping.ManyToOne value,
XProperty property,
boolean hasSpecjManyToOne,
String propertyName) {
@ -265,7 +276,7 @@ public class ToOneBinder {
propertyBinder.setAccessType( inferredData.getDefaultAccess() );
propertyBinder.setCascade( cascadeStrategy );
propertyBinder.setProperty( property );
propertyBinder.setXToMany( true );
propertyBinder.setToMany( true );
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
@ -346,7 +357,7 @@ public class ToOneBinder {
// LazyToOne takes precedent
final LazyToOne lazy = property.getAnnotation( LazyToOne.class );
boolean eager = lazy.value() == LazyToOneOption.FALSE;
if ( eager && fetchType == FetchType.LAZY ) {
if ( eager && fetchType == LAZY ) {
// conflicts with non-default setting
throw new AnnotationException("Association '" + getPath(propertyHolder, inferredData)
+ "' is marked 'fetch=LAZY' and '@LazyToOne(FALSE)'");
@ -354,7 +365,7 @@ public class ToOneBinder {
return eager;
}
else {
return fetchType == FetchType.EAGER;
return fetchType == EAGER;
}
}
@ -579,20 +590,20 @@ public class ToOneBinder {
}
else {
ConstraintMode mode = joinColumns.value();
return mode == ConstraintMode.NO_CONSTRAINT
|| mode == ConstraintMode.PROVIDER_DEFAULT && context.getBuildingOptions().isNoConstraintByDefault();
return mode == NO_CONSTRAINT
|| mode == PROVIDER_DEFAULT && context.getBuildingOptions().isNoConstraintByDefault();
}
}
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
return AnnotationBinder.isDefault( targetEntity, context )
return isDefault( targetEntity, context )
? propertyData.getClassOrElementName()
: targetEntity.getName();
}
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) {
final XClass targetEntity = getTargetEntity( propertyData, context );
return AnnotationBinder.isDefault( targetEntity, context )
return isDefault( targetEntity, context )
? propertyData.getClassOrElementName()
: targetEntity.getName();
}
@ -613,4 +624,14 @@ public class ToOneBinder {
}
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);
@LogMessage(level = WARN)
@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)
@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)
void ignoreNotFoundWithFetchTypeLazy(String entity, String association);
@LogMessage(level = INFO)

View File

@ -13,7 +13,7 @@ import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
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.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
@ -44,10 +44,10 @@ public class JoinFormulaManyToOneNotIgnoreLazyFetchingTest extends BaseEntityMan
@Rule
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

View File

@ -13,7 +13,7 @@ import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
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.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
@ -44,10 +44,10 @@ public class JoinFormulaOneToOneNotIgnoreLazyFetchingTest extends BaseEntityMana
@Rule
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

View File

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