more binding work related to OneToOne and ManyToOne to ToOneBinder where it belongs

and stuff in common goes to BinderHelper
This commit is contained in:
Gavin King 2022-10-28 22:18:55 +02:00
parent 8b3030aa8b
commit ed65962fb3
6 changed files with 832 additions and 748 deletions

View File

@ -12,8 +12,6 @@ import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -21,12 +19,10 @@ import java.util.Map;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.CollectionTypeRegistration;
import org.hibernate.annotations.CollectionTypeRegistrations;
import org.hibernate.annotations.Columns;
@ -37,7 +33,6 @@ import org.hibernate.annotations.ConverterRegistration;
import org.hibernate.annotations.ConverterRegistrations;
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfiles;
import org.hibernate.annotations.FilterDef;
@ -52,11 +47,8 @@ import org.hibernate.annotations.JavaTypeRegistrations;
import org.hibernate.annotations.JdbcTypeRegistration;
import org.hibernate.annotations.JdbcTypeRegistrations;
import org.hibernate.annotations.LazyGroup;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@ -91,17 +83,14 @@ import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.GenericsHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Constraint;
import org.hibernate.mapping.IdentifierGeneratorCreator;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
@ -126,14 +115,12 @@ import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
@ -153,8 +140,6 @@ import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.SequenceGenerators;
import jakarta.persistence.SqlResultSetMapping;
@ -168,14 +153,11 @@ import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation;
import static org.hibernate.cfg.BinderHelper.getPath;
import static org.hibernate.cfg.BinderHelper.getPropertyOverriddenByMapperOrMapsId;
import static org.hibernate.cfg.BinderHelper.hasToOneAnnotation;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
import static org.hibernate.cfg.BinderHelper.makeIdGenerator;
import static org.hibernate.cfg.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.cfg.InheritanceState.getSuperclassInheritanceState;
import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.internal.CoreLogging.messageLogger;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
/**
@ -1176,7 +1158,7 @@ public final class AnnotationBinder {
}
else {
if ( property.isAnnotationPresent( ManyToOne.class ) ) {
bindManyToOne(
ToOneBinder.bindManyToOne(
propertyHolder,
inferredData,
isIdentifierMapper,
@ -1189,7 +1171,7 @@ public final class AnnotationBinder {
);
}
else if ( property.isAnnotationPresent( OneToOne.class ) ) {
bindOneToOne(
ToOneBinder.bindOneToOne(
propertyHolder,
inferredData,
isIdentifierMapper,
@ -1596,7 +1578,7 @@ public final class AnnotationBinder {
}
}
bindAny(
getCascadeStrategy( null, hibernateCascade, false, forcePersist),
BinderHelper.getCascadeStrategy( null, hibernateCascade, false, forcePersist),
//@Any has not cascade attribute
joinColumns,
onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(),
@ -1609,126 +1591,6 @@ public final class AnnotationBinder {
);
}
private static void bindOneToOne(PropertyHolder propertyHolder, PropertyData inferredData, boolean isIdentifierMapper, boolean inSecondPass, MetadataBuildingContext context, XProperty property, AnnotatedJoinColumn[] joinColumns, PropertyBinder propertyBinder, boolean forcePersist) {
OneToOne ann = property.getAnnotation( OneToOne.class );
//check validity
if ( property.isAnnotationPresent( Column.class )
|| property.isAnnotationPresent( Columns.class ) ) {
throw new AnnotationException(
"Property '"+ getPath( propertyHolder, inferredData )
+ "' is a '@OneToOne' association and may not use '@Column' to specify column mappings (use '@PrimaryKeyJoinColumn' instead)"
);
}
//FIXME support a proper PKJCs
boolean trueOneToOne = property.isAnnotationPresent( PrimaryKeyJoinColumn.class )
|| property.isAnnotationPresent( PrimaryKeyJoinColumns.class );
Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class );
NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
final boolean hasNotFoundAction = notFoundAction != null;
// MapsId means the columns belong to the pk;
// A @MapsId association (obviously) must be non-null when the entity is first persisted.
// If a @MapsId association is not mapped with @NotFound(IGNORE), then the association
// is mandatory (even if the association has optional=true).
// If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then
// the association is optional.
// @OneToOne(optional = true) with @PKJC makes the association optional.
final boolean mandatory = !ann.optional()
|| property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( MapsId.class ) && !hasNotFoundAction;
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
JoinTable assocTable = propertyHolder.getJoinTable(property);
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
if ( hasNotFoundAction ) {
join.disableForeignKeyCreation();
}
for ( AnnotatedJoinColumn joinColumn : joinColumns) {
joinColumn.setExplicitTableName( join.getTable().getName() );
}
}
bindOneToOne(
getCascadeStrategy( ann.cascade(), hibernateCascade, ann.orphanRemoval(), forcePersist),
joinColumns,
!mandatory,
getFetchMode( ann.fetch() ),
notFoundAction,
onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(),
ToOneBinder.getTargetEntity(inferredData, context),
propertyHolder,
inferredData,
ann.mappedBy(),
trueOneToOne,
isIdentifierMapper,
inSecondPass,
propertyBinder,
context
);
}
private static void bindManyToOne(
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean isIdentifierMapper,
boolean inSecondPass,
MetadataBuildingContext context,
XProperty property,
AnnotatedJoinColumn[] joinColumns,
PropertyBinder propertyBinder,
boolean forcePersist) {
ManyToOne ann = property.getAnnotation( ManyToOne.class );
//check validity
if ( property.isAnnotationPresent( Column.class )
|| property.isAnnotationPresent( Columns.class ) ) {
throw new AnnotationException(
"Property '"+ getPath( propertyHolder, inferredData )
+ "' is a '@ManyToOne' association and may not use '@Column' to specify column mappings (use '@JoinColumn' instead)"
);
}
Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class );
NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
JoinTable assocTable = propertyHolder.getJoinTable(property);
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
for ( AnnotatedJoinColumn joinColumn : joinColumns) {
joinColumn.setExplicitTableName( join.getTable().getName() );
}
}
// MapsId means the columns belong to the pk;
// A @MapsId association (obviously) must be non-null when the entity is first persisted.
// If a @MapsId association is not mapped with @NotFound(IGNORE), then the association
// is mandatory (even if the association has optional=true).
// If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then
// the association is optional.
final boolean mandatory = !ann.optional()
|| property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( MapsId.class ) && notFoundAction != null;
bindManyToOne(
getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist),
joinColumns,
!mandatory,
notFoundAction,
onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(),
ToOneBinder.getTargetEntity(inferredData, context),
propertyHolder,
inferredData,
false,
isIdentifierMapper,
inSecondPass,
propertyBinder,
context
);
}
private static void addIndexes(
boolean inSecondPass,
XProperty property,
@ -2335,287 +2197,7 @@ public final class AnnotationBinder {
return baseClassElements.get( 0 );
}
private static void bindManyToOne(
String cascadeStrategy,
AnnotatedJoinColumn[] columns,
boolean optional,
NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean unique,
boolean isIdentifierMapper,
boolean inSecondPass,
PropertyBinder propertyBinder,
MetadataBuildingContext context) {
//All FK columns should be in the same table
final org.hibernate.mapping.ManyToOne value = new org.hibernate.mapping.ManyToOne( context, columns[0].getTable() );
// This is a @OneToOne mapped to a physical o.h.mapping.ManyToOne
if ( unique ) {
value.markAsLogicalOneToOne();
}
value.setReferencedEntityName( ToOneBinder.getReferenceEntityName( inferredData, targetEntity, context ) );
final XProperty property = inferredData.getProperty();
defineFetchingStrategy( value, property );
//value.setFetchMode( fetchMode );
value.setNotFoundAction( notFoundAction );
value.setCascadeDeleteEnabled( cascadeOnDelete );
//value.setLazy( fetchMode != FetchMode.JOIN );
if ( !optional ) {
for ( AnnotatedJoinColumn column : columns ) {
column.setNullable( false );
}
}
if ( property.isAnnotationPresent( MapsId.class ) ) {
//read only
for ( AnnotatedJoinColumn column : columns ) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
//Make sure that JPA1 key-many-to-one columns are read only too
boolean hasSpecjManyToOne=false;
if ( context.getBuildingOptions().isSpecjProprietarySyntaxEnabled() ) {
String columnName = "";
for ( XProperty prop : inferredData.getDeclaringClass()
.getDeclaredProperties( AccessType.FIELD.getType() ) ) {
if ( prop.isAnnotationPresent( Id.class ) && prop.isAnnotationPresent( Column.class ) ) {
columnName = prop.getAnnotation( Column.class ).name();
}
if ( property.isAnnotationPresent( ManyToOne.class ) && joinColumn != null
&& ! isEmptyAnnotationValue( joinColumn.name() )
&& joinColumn.name().equals( columnName )
&& !property.isAnnotationPresent( MapsId.class ) ) {
hasSpecjManyToOne = true;
for ( AnnotatedJoinColumn column : columns ) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
}
}
value.setTypeName( inferredData.getClassOrElementName() );
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
final String fullPath = qualify( propertyHolder.getPath(), propertyName );
bindForeignKeyNameAndDefinition(
value,
property,
propertyHolder.getOverriddenForeignKey( fullPath ),
joinColumn,
joinColumns,
context
);
final FkSecondPass secondPass = new ToOneFkSecondPass(
value,
columns,
!optional && unique, //cannot have nullable and unique on certain DBs like Derby
propertyHolder.getPersistentClass(),
fullPath,
context
);
if ( inSecondPass ) {
secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() );
}
else {
context.getMetadataCollector().addSecondPass( secondPass );
}
AnnotatedColumn.checkPropertyConsistency( columns, qualify( propertyHolder.getEntityName(), propertyName ) );
//PropertyBinder binder = new PropertyBinder();
propertyBinder.setName( propertyName );
propertyBinder.setValue( value );
//binder.setCascade(cascadeStrategy);
if ( isIdentifierMapper ) {
propertyBinder.setInsertable( false );
propertyBinder.setUpdatable( false );
}
else if (hasSpecjManyToOne) {
propertyBinder.setInsertable( false );
propertyBinder.setUpdatable( false );
}
else {
propertyBinder.setInsertable( columns[0].isInsertable() );
propertyBinder.setUpdatable( columns[0].isUpdatable() );
}
propertyBinder.setColumns( columns );
propertyBinder.setAccessType( inferredData.getDefaultAccess() );
propertyBinder.setCascade( cascadeStrategy );
propertyBinder.setProperty( property );
propertyBinder.setXToMany( true );
propertyBinder.makePropertyAndBind().setOptional( optional && isNullable( joinColumns, joinColumn ) );
}
private static boolean isNullable(JoinColumns joinColumns, JoinColumn joinColumn) {
if ( joinColumn != null ) {
return joinColumn.nullable();
}
else if ( joinColumns != null ) {
final JoinColumn[] columns = joinColumns.value();
for ( int i = 0; i < columns.length; i++ ) {
if ( columns[i].nullable() ) {
return true;
}
}
return false;
}
else {
return true;
}
}
static void defineFetchingStrategy(ToOne toOne, XProperty property) {
final FetchType fetchType = getJpaFetchType( property );
LazyToOne lazy = property.getAnnotation( LazyToOne.class );
NotFound notFound = property.getAnnotation( NotFound.class );
if ( notFound != null ) {
toOne.setLazy( false );
toOne.setUnwrapProxy( true );
}
else if ( lazy != null ) {
toOne.setLazy( lazy.value() != LazyToOneOption.FALSE );
toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
}
else {
toOne.setLazy( fetchType == FetchType.LAZY );
toOne.setUnwrapProxy( fetchType != FetchType.LAZY );
toOne.setUnwrapProxyImplicit( true );
}
Fetch fetch = property.getAnnotation( Fetch.class );
if ( fetch != null ) {
// Hibernate @Fetch annotation takes precedence
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
toOne.setFetchMode( FetchMode.JOIN );
toOne.setLazy( false );
toOne.setUnwrapProxy( false );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
toOne.setFetchMode( FetchMode.SELECT );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
throw new AnnotationException( "Association '" + property.getName()
+ "' is annotated '@Fetch(SUBSELECT)' but is not many-valued");
}
else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
}
}
else {
toOne.setFetchMode( getFetchMode( fetchType ) );
}
}
private static FetchType getJpaFetchType(XProperty property) {
ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
OneToOne oneToOne = property.getAnnotation( OneToOne.class );
if ( manyToOne != null ) {
return manyToOne.fetch();
}
else if ( oneToOne != null ) {
return oneToOne.fetch();
}
else {
throw new AssertionFailure("Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne");
}
}
private static void bindOneToOne(
String cascadeStrategy,
AnnotatedJoinColumn[] joinColumns,
boolean optional,
FetchMode fetchMode,
NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
PropertyData inferredData,
String mappedBy,
boolean trueOneToOne,
boolean isIdentifierMapper,
boolean inSecondPass,
PropertyBinder propertyBinder,
MetadataBuildingContext context) {
//column.getTable() => persistentClass.getTable()
final String propertyName = inferredData.getPropertyName();
LOG.tracev( "Fetching {0} with {1}", propertyName, fetchMode );
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne ) || !isEmptyAnnotationValue( mappedBy ) ) {
//is a true one-to-one
//FIXME referencedColumnName ignored => ordering may fail.
OneToOneSecondPass secondPass = new OneToOneSecondPass(
mappedBy,
propertyHolder.getEntityName(),
propertyName,
propertyHolder,
inferredData,
targetEntity,
notFoundAction,
cascadeOnDelete,
optional,
cascadeStrategy,
joinColumns,
context
);
if ( inSecondPass ) {
secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() );
}
else {
context.getMetadataCollector().addSecondPass( secondPass, isEmptyAnnotationValue( mappedBy ) );
}
}
else {
//has a FK on the table
bindManyToOne(
cascadeStrategy, joinColumns, optional, notFoundAction, cascadeOnDelete,
targetEntity,
propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass,
propertyBinder, context
);
}
}
private static boolean isMapToPK(AnnotatedJoinColumn[] joinColumns, PropertyHolder propertyHolder, boolean trueOneToOne) {
if ( trueOneToOne ) {
return true;
}
else {
//try to find a hidden true one to one (FK == PK columns)
KeyValue identifier = propertyHolder.getIdentifier();
if ( identifier == null ) {
//this is a @OneToOne in an @EmbeddedId (the persistentClass.identifier is not set yet, it's being built)
//by definition the PK cannot refer to itself so it cannot map to itself
return false;
}
else {
List<String> idColumnNames = new ArrayList<>();
if ( identifier.getColumnSpan() != joinColumns.length ) {
return false;
}
else {
for ( org.hibernate.mapping.Column currentColumn: identifier.getColumns() ) {
idColumnNames.add( currentColumn.getName() );
}
for ( AnnotatedJoinColumn col: joinColumns) {
if ( !idColumnNames.contains( col.getMappingColumn().getName() ) ) {
return false;
}
}
return true;
}
}
}
}
private static void bindAny(
String cascadeStrategy,
@ -2669,148 +2251,6 @@ public final class AnnotationBinder {
propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() );
}
private static EnumSet<CascadeType> convertToHibernateCascadeType(jakarta.persistence.CascadeType[] ejbCascades) {
final EnumSet<CascadeType> cascadeTypes = EnumSet.noneOf( CascadeType.class );
if ( ejbCascades != null && ejbCascades.length > 0 ) {
for ( jakarta.persistence.CascadeType cascade: ejbCascades ) {
cascadeTypes.add( convertCascadeType( cascade ) );
}
}
return cascadeTypes;
}
private static CascadeType convertCascadeType(jakarta.persistence.CascadeType cascade) {
switch (cascade) {
case ALL:
return CascadeType.ALL;
case PERSIST:
return CascadeType.PERSIST;
case MERGE:
return CascadeType.MERGE;
case REMOVE:
return CascadeType.REMOVE;
case REFRESH:
return CascadeType.REFRESH;
case DETACH:
return CascadeType.DETACH;
default:
throw new AssertionFailure("unknown cascade type: " + cascade);
}
}
public static String getCascadeStrategy(
jakarta.persistence.CascadeType[] ejbCascades,
Cascade hibernateCascadeAnnotation,
boolean orphanRemoval,
boolean forcePersist) {
EnumSet<CascadeType> cascadeTypes = convertToHibernateCascadeType( ejbCascades );
CascadeType[] hibernateCascades = hibernateCascadeAnnotation == null ? null : hibernateCascadeAnnotation.value();
if ( hibernateCascades != null && hibernateCascades.length > 0 ) {
cascadeTypes.addAll( Arrays.asList( hibernateCascades ) );
}
if ( orphanRemoval ) {
cascadeTypes.add( CascadeType.DELETE_ORPHAN );
cascadeTypes.add( CascadeType.REMOVE );
}
if ( forcePersist ) {
cascadeTypes.add( CascadeType.PERSIST );
}
return renderCascadeTypeList( cascadeTypes );
}
private static String renderCascadeTypeList(EnumSet<CascadeType> cascadeTypes) {
StringBuilder cascade = new StringBuilder();
for ( CascadeType cascadeType : cascadeTypes) {
switch ( cascadeType ) {
case ALL:
cascade.append( "," ).append( "all" );
break;
case SAVE_UPDATE:
cascade.append( "," ).append( "save-update" );
break;
case PERSIST:
cascade.append( "," ).append( "persist" );
break;
case MERGE:
cascade.append( "," ).append( "merge" );
break;
case LOCK:
cascade.append( "," ).append( "lock" );
break;
case REFRESH:
cascade.append( "," ).append( "refresh" );
break;
case REPLICATE:
cascade.append( "," ).append( "replicate" );
break;
case DETACH:
cascade.append( "," ).append( "evict" );
break;
case DELETE:
case REMOVE:
cascade.append( "," ).append( "delete" );
break;
case DELETE_ORPHAN:
cascade.append( "," ).append( "delete-orphan" );
break;
}
}
return cascade.length() > 0 ? cascade.substring( 1 ) : "none";
}
public static FetchMode getFetchMode(FetchType fetch) {
return fetch == FetchType.EAGER ? FetchMode.JOIN : FetchMode.SELECT;
}
public static void bindForeignKeyNameAndDefinition(
SimpleValue value,
XProperty property,
ForeignKey fkOverride,
JoinColumn joinColumn,
JoinColumns joinColumns,
MetadataBuildingContext context) {
if ( property.getAnnotation( NotFound.class ) != null ) {
// supersedes all others
value.disableForeignKey();
}
else {
if ( joinColumn!=null && noConstraint( joinColumn.foreignKey(), context )
|| joinColumns!=null && noConstraint( joinColumns.foreignKey(), context ) ) {
value.disableForeignKey();
}
else {
final org.hibernate.annotations.ForeignKey fk = property.getAnnotation( org.hibernate.annotations.ForeignKey.class );
if ( fk != null && StringHelper.isNotEmpty( fk.name() ) ) {
value.setForeignKeyName( fk.name() );
}
else {
if ( noConstraint( fkOverride, context) ) {
value.disableForeignKey();
}
else if ( fkOverride != null ) {
value.setForeignKeyName( nullIfEmpty( fkOverride.name() ) );
value.setForeignKeyDefinition( nullIfEmpty( fkOverride.foreignKeyDefinition() ) );
}
else if ( joinColumns != null ) {
value.setForeignKeyName( nullIfEmpty( joinColumns.foreignKey().name() ) );
value.setForeignKeyDefinition( nullIfEmpty( joinColumns.foreignKey().foreignKeyDefinition() ) );
}
else if ( joinColumn != null ) {
value.setForeignKeyName( nullIfEmpty( joinColumn.foreignKey().name() ) );
value.setForeignKeyDefinition( nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
}
}
}
}
}
private static boolean noConstraint(ForeignKey joinColumns, MetadataBuildingContext context) {
return joinColumns != null
&& ( joinColumns.value() == ConstraintMode.NO_CONSTRAINT
|| joinColumns.value() == ConstraintMode.PROVIDER_DEFAULT
&& context.getBuildingOptions().isNoConstraintByDefault() );
}
public static HashMap<String, IdentifierGeneratorDefinition> buildGenerators(
XAnnotatedElement annElt,
MetadataBuildingContext context) {
@ -2942,7 +2382,7 @@ public final class AnnotationBinder {
|| property.isAnnotationPresent(ManyToMany.class);
}
private static void matchIgnoreNotFoundWithFetchType(
static void matchIgnoreNotFoundWithFetchType(
String entity,
String association,
NotFoundAction notFoundAction,

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -22,13 +23,17 @@ import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.annotations.AnyDiscriminatorValue;
import org.hibernate.annotations.AnyDiscriminatorValues;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.DialectOverride;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.SqlFragmentAlias;
@ -169,20 +174,13 @@ public class BinderHelper {
boolean inverse,
MetadataBuildingContext context) {
// TODO: instead of pulling info like the property name and whether
// it's on the owning side off the zeroth column coming in, we
// should receive it directly in the argument list, or from a
// Property instance
final AnnotatedJoinColumn firstColumn = columns[0];
if ( !firstColumn.isImplicit()
// not necessary for a primary key reference
&& checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) {
// this work is not necessary for a primary key reference
if ( checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) { // && !firstColumn.isImplicit()
// all the columns have to belong to the same table;
// figure out which table has the columns by looking
// for a PersistentClass or Join in the hierarchy of
// the target entity which has the first column
final Object columnOwner = findColumnOwner( targetEntity, firstColumn.getReferencedColumn(), context );
final Object columnOwner = findColumnOwner( targetEntity, columns[0].getReferencedColumn(), context );
checkColumnInSameTable( columns, targetEntity, associatedEntity, context, columnOwner );
// find all properties mapped to each column
final List<Property> properties = findPropertiesByColumns( columnOwner, columns, associatedEntity, context );
@ -203,8 +201,10 @@ public class BinderHelper {
targetEntity,
value,
inverse,
firstColumn.getPropertyHolder().getPersistentClass(),
firstColumn.getPropertyName(),
associatedEntity,
propertyName,
// columns[0].getPropertyHolder().getPersistentClass(),
// columns[0].getPropertyName(),
property.getName(),
context
);
@ -1261,4 +1261,98 @@ public class BinderHelper {
}
return element.getAnnotation( annotationType );
}
public static FetchMode getFetchMode(FetchType fetch) {
return fetch == FetchType.EAGER ? FetchMode.JOIN : FetchMode.SELECT;
}
private static CascadeType convertCascadeType(jakarta.persistence.CascadeType cascade) {
switch (cascade) {
case ALL:
return CascadeType.ALL;
case PERSIST:
return CascadeType.PERSIST;
case MERGE:
return CascadeType.MERGE;
case REMOVE:
return CascadeType.REMOVE;
case REFRESH:
return CascadeType.REFRESH;
case DETACH:
return CascadeType.DETACH;
default:
throw new AssertionFailure("unknown cascade type: " + cascade);
}
}
private static EnumSet<CascadeType> convertToHibernateCascadeType(jakarta.persistence.CascadeType[] ejbCascades) {
final EnumSet<CascadeType> cascadeTypes = EnumSet.noneOf( CascadeType.class );
if ( ejbCascades != null && ejbCascades.length > 0 ) {
for ( jakarta.persistence.CascadeType cascade: ejbCascades ) {
cascadeTypes.add( convertCascadeType( cascade ) );
}
}
return cascadeTypes;
}
public static String getCascadeStrategy(
jakarta.persistence.CascadeType[] ejbCascades,
Cascade hibernateCascadeAnnotation,
boolean orphanRemoval,
boolean forcePersist) {
EnumSet<CascadeType> cascadeTypes = convertToHibernateCascadeType( ejbCascades );
CascadeType[] hibernateCascades = hibernateCascadeAnnotation == null ? null : hibernateCascadeAnnotation.value();
if ( hibernateCascades != null && hibernateCascades.length > 0 ) {
cascadeTypes.addAll( Arrays.asList( hibernateCascades ) );
}
if ( orphanRemoval ) {
cascadeTypes.add( CascadeType.DELETE_ORPHAN );
cascadeTypes.add( CascadeType.REMOVE );
}
if ( forcePersist ) {
cascadeTypes.add( CascadeType.PERSIST );
}
return renderCascadeTypeList( cascadeTypes );
}
private static String renderCascadeTypeList(EnumSet<CascadeType> cascadeTypes) {
StringBuilder cascade = new StringBuilder();
for ( CascadeType cascadeType : cascadeTypes) {
switch ( cascadeType ) {
case ALL:
cascade.append( "," ).append( "all" );
break;
case SAVE_UPDATE:
cascade.append( "," ).append( "save-update" );
break;
case PERSIST:
cascade.append( "," ).append( "persist" );
break;
case MERGE:
cascade.append( "," ).append( "merge" );
break;
case LOCK:
cascade.append( "," ).append( "lock" );
break;
case REFRESH:
cascade.append( "," ).append( "refresh" );
break;
case REPLICATE:
cascade.append( "," ).append( "replicate" );
break;
case DETACH:
cascade.append( "," ).append( "evict" );
break;
case DELETE:
case REMOVE:
cascade.append( "," ).append( "delete" );
break;
case DELETE_ORPHAN:
cascade.append( "," ).append( "delete-orphan" );
break;
}
}
return cascade.length() > 0 ? cascade.substring( 1 ) : "none";
}
}

View File

@ -8,20 +8,21 @@ package org.hibernate.cfg;
import java.util.Map;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.ForeignKey;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.LazyGroup;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.annotations.PropertyBinder;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.PersistentClass;
@ -32,11 +33,13 @@ import org.hibernate.type.ForeignKeyDirection;
import static org.hibernate.cfg.BinderHelper.findPropertyByName;
import static org.hibernate.cfg.BinderHelper.getPath;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
import static org.hibernate.cfg.ToOneBinder.bindForeignKeyNameAndDefinition;
import static org.hibernate.cfg.ToOneBinder.getReferenceEntityName;
import static org.hibernate.internal.util.StringHelper.qualify;
/**
* We have to handle OneToOne in a second pass because:
* -
* We have to handle {@link jakarta.persistence.OneToOne} associations
* in a second pass.
*/
public class OneToOneSecondPass implements SecondPass {
private final MetadataBuildingContext buildingContext;
@ -82,167 +85,182 @@ public class OneToOneSecondPass implements SecondPass {
//TODO refactor this code, there is a lot of duplication in this method
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
OneToOne value = new OneToOne(
final OneToOne value = new OneToOne(
buildingContext,
propertyHolder.getTable(),
propertyHolder.getPersistentClass()
);
final String propertyName = inferredData.getPropertyName();
value.setPropertyName( propertyName );
String referencedEntityName = ToOneBinder.getReferenceEntityName( inferredData, targetEntity, buildingContext );
final String referencedEntityName = getReferenceEntityName( inferredData, targetEntity, buildingContext );
value.setReferencedEntityName( referencedEntityName );
AnnotationBinder.defineFetchingStrategy( value, inferredData.getProperty() );
XProperty prop = inferredData.getProperty();
ToOneBinder.defineFetchingStrategy( value, prop );
//value.setFetchMode( fetchMode );
value.setCascadeDeleteEnabled( cascadeOnDelete );
//value.setLazy( fetchMode != FetchMode.JOIN );
value.setConstrained( !optional );
final ForeignKeyDirection foreignKeyDirection = !isEmptyAnnotationValue( mappedBy )
? ForeignKeyDirection.TO_PARENT
: ForeignKeyDirection.FROM_PARENT;
value.setForeignKeyType(foreignKeyDirection);
AnnotationBinder.bindForeignKeyNameAndDefinition(
value,
inferredData.getProperty(),
inferredData.getProperty().getAnnotation( jakarta.persistence.ForeignKey.class ),
inferredData.getProperty().getAnnotation( JoinColumn.class ),
inferredData.getProperty().getAnnotation( JoinColumns.class),
buildingContext
);
value.setForeignKeyType( getForeignKeyDirection() );
bindForeignKeyNameAndDefinition( value, prop, prop.getAnnotation( ForeignKey.class ), buildingContext );
PropertyBinder binder = new PropertyBinder();
final PropertyBinder binder = new PropertyBinder();
binder.setName( propertyName );
binder.setProperty( inferredData.getProperty() );
binder.setProperty(prop);
binder.setValue( value );
binder.setCascade( cascadeStrategy );
binder.setAccessType( inferredData.getDefaultAccess() );
final LazyGroup lazyGroupAnnotation = inferredData.getProperty().getAnnotation( LazyGroup.class );
final LazyGroup lazyGroupAnnotation = prop.getAnnotation( LazyGroup.class );
if ( lazyGroupAnnotation != null ) {
binder.setLazyGroup( lazyGroupAnnotation.value() );
}
Property property = binder.makeProperty();
final Property property = binder.makeProperty();
property.setOptional( optional );
if ( isEmptyAnnotationValue( mappedBy ) ) {
// we need to check if the columns are in the right order
// if not, then we need to create a many to one and formula
// but actually, since entities linked by a one to one need
// to share the same composite id class, this cannot happen
boolean rightOrder = true;
if ( rightOrder ) {
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
value,
joinColumns,
!optional, //cannot have nullable and unique on certain DBs
propertyHolder.getPersistentClass(),
qualify( propertyHolder.getPath(), propertyName ),
buildingContext
);
secondPass.doSecondPass( persistentClasses );
//no column associated since its a one to one
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
}
// else {
//this is a many to one with Formula
// }
bindUnowned( persistentClasses, value, propertyName, property );
}
else {
value.setMappedByProperty( mappedBy );
PersistentClass otherSide = persistentClasses.get( value.getReferencedEntityName() );
if ( otherSide == null ) {
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
+ "' targets unknown entity type '" + value.getReferencedEntityName() + "'" );
}
Property otherSideProperty;
try {
otherSideProperty = findPropertyByName( otherSide, mappedBy );
}
catch (MappingException e) {
otherSideProperty = null;
}
if ( otherSideProperty == null ) {
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' is 'mappedBy' a property named '" + mappedBy
+ "' which does not exist in the target entity type '" + value.getReferencedEntityName() + "'" );
}
if ( otherSideProperty.getValue() instanceof OneToOne ) {
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
}
else if ( otherSideProperty.getValue() instanceof ManyToOne ) {
Join otherSideJoin = null;
for ( Join otherSideJoinValue : otherSide.getJoins() ) {
if ( otherSideJoinValue.containsProperty( otherSideProperty ) ) {
otherSideJoin = otherSideJoinValue;
break;
}
}
if ( otherSideJoin != null ) {
//@OneToOne @JoinTable
Join mappedByJoin = buildJoinFromMappedBySide(
persistentClasses.get( ownerEntity ), otherSideProperty, otherSideJoin
);
ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
//FIXME use ignore not found here
manyToOne.setNotFoundAction( notFoundAction );
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
manyToOne.setFetchMode( value.getFetchMode() );
manyToOne.setLazy( value.isLazy() );
manyToOne.setReferencedEntityName( value.getReferencedEntityName() );
manyToOne.setUnwrapProxy( value.isUnwrapProxy() );
manyToOne.markAsLogicalOneToOne();
property.setValue( manyToOne );
for ( Column column: otherSideJoin.getKey().getColumns() ) {
Column copy = new Column();
copy.setLength( column.getLength() );
copy.setScale( column.getScale() );
copy.setValue( manyToOne );
copy.setName( column.getQuotedName() );
copy.setNullable( column.isNullable() );
copy.setPrecision( column.getPrecision() );
copy.setUnique( column.isUnique() );
copy.setSqlType( column.getSqlType() );
copy.setCheckConstraint( column.getCheckConstraint() );
copy.setComment( column.getComment() );
copy.setDefaultValue( column.getDefaultValue() );
copy.setGeneratedAs(column.getGeneratedAs() );
manyToOne.addColumn( copy );
}
mappedByJoin.addProperty( property );
}
else {
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
}
value.setReferencedPropertyName( mappedBy );
// HHH-6813
// Foo: @Id long id, @OneToOne(mappedBy="foo") Bar bar
// Bar: @Id @OneToOne Foo foo
boolean referenceToPrimaryKey = mappedBy == null
|| otherSide.getIdentifier() instanceof Component
&& ! ( (Component) otherSide.getIdentifier() ).hasProperty( mappedBy );
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
String propertyRef = value.getReferencedPropertyName();
if ( propertyRef != null ) {
buildingContext.getMetadataCollector().addUniquePropertyReference(
value.getReferencedEntityName(),
propertyRef
);
}
}
else {
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' is 'mappedBy' a property named '" + mappedBy
+ "' of the target entity type '" + value.getReferencedEntityName()
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
}
bindOwned( persistentClasses, value, property );
}
value.sortProperties();
}
private ForeignKeyDirection getForeignKeyDirection() {
return !isEmptyAnnotationValue( mappedBy ) ? ForeignKeyDirection.TO_PARENT : ForeignKeyDirection.FROM_PARENT;
}
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne value, Property property) {
value.setMappedByProperty( mappedBy );
final PersistentClass targetEntity = persistentClasses.get( value.getReferencedEntityName() );
if ( targetEntity == null ) {
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
+ "' targets unknown entity type '" + value.getReferencedEntityName() + "'" );
}
final Property targetProperty = targetProperty( value, targetEntity );
if ( targetProperty.getValue() instanceof OneToOne ) {
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
}
else if ( targetProperty.getValue() instanceof ManyToOne ) {
bindTargetManyToOne( persistentClasses, value, property, targetEntity, targetProperty );
}
else {
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' is 'mappedBy' a property named '" + mappedBy
+ "' of the target entity type '" + value.getReferencedEntityName()
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
}
}
private void bindTargetManyToOne(
Map<String, PersistentClass> persistentClasses,
OneToOne value,
Property property,
PersistentClass targetEntity,
Property targetProperty) {
Join otherSideJoin = null;
for ( Join otherSideJoinValue : targetEntity.getJoins() ) {
if ( otherSideJoinValue.containsProperty(targetProperty) ) {
otherSideJoin = otherSideJoinValue;
break;
}
}
if ( otherSideJoin != null ) {
//@OneToOne @JoinTable
final Join mappedByJoin = buildJoinFromMappedBySide(
persistentClasses.get( ownerEntity ), targetProperty, otherSideJoin
);
final ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
//FIXME use ignore not found here
manyToOne.setNotFoundAction( notFoundAction );
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
manyToOne.setFetchMode( value.getFetchMode() );
manyToOne.setLazy( value.isLazy() );
manyToOne.setReferencedEntityName( value.getReferencedEntityName() );
manyToOne.setUnwrapProxy( value.isUnwrapProxy() );
manyToOne.markAsLogicalOneToOne();
property.setValue( manyToOne );
for ( Column column: otherSideJoin.getKey().getColumns() ) {
Column copy = new Column();
copy.setLength( column.getLength() );
copy.setScale( column.getScale() );
copy.setValue( manyToOne );
copy.setName( column.getQuotedName() );
copy.setNullable( column.isNullable() );
copy.setPrecision( column.getPrecision() );
copy.setUnique( column.isUnique() );
copy.setSqlType( column.getSqlType() );
copy.setCheckConstraint( column.getCheckConstraint() );
copy.setComment( column.getComment() );
copy.setDefaultValue( column.getDefaultValue() );
copy.setGeneratedAs( column.getGeneratedAs() );
manyToOne.addColumn( copy );
}
mappedByJoin.addProperty( property );
}
else {
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
}
value.setReferencedPropertyName( mappedBy );
// HHH-6813
// Foo: @Id long id, @OneToOne(mappedBy="foo") Bar bar
// Bar: @Id @OneToOne Foo foo
final KeyValue targetEntityIdentifier = targetEntity.getIdentifier();
boolean referenceToPrimaryKey = mappedBy == null
|| targetEntityIdentifier instanceof Component
&& !( (Component) targetEntityIdentifier ).hasProperty( mappedBy );
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
final String propertyRef = value.getReferencedPropertyName();
if ( propertyRef != null ) {
buildingContext.getMetadataCollector()
.addUniquePropertyReference( value.getReferencedEntityName(), propertyRef );
}
}
private Property targetProperty(OneToOne value, PersistentClass targetEntity) {
try {
Property targetProperty = findPropertyByName( targetEntity, mappedBy );
if ( targetProperty != null ) {
return targetProperty;
}
}
catch (MappingException e) {
// swallow it
}
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' is 'mappedBy' a property named '" + mappedBy
+ "' which does not exist in the target entity type '" + value.getReferencedEntityName() + "'" );
}
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne value, String propertyName, Property property) {
// we need to check if the columns are in the right order
// if not, then we need to create a many to one and formula
// but actually, since entities linked by a one to one need
// to share the same composite id class, this cannot happen
boolean rightOrder = true;
if ( rightOrder ) {
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
value,
joinColumns,
!optional, //cannot have nullable and unique on certain DBs
propertyHolder.getPersistentClass(),
qualify( propertyHolder.getPath(), propertyName),
buildingContext
);
secondPass.doSecondPass(persistentClasses);
//no column associated since its a one to one
propertyHolder.addProperty(property, inferredData.getDeclaringClass() );
}
// else {
// this is a @ManyToOne with Formula
// }
}
/**
* Builds the <code>Join</code> instance for the mapped by side of a <i>OneToOne</i> association using
* a join table.

View File

@ -6,24 +6,562 @@
*/
package org.hibernate.cfg;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.annotations.PropertyBinder;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import java.util.ArrayList;
import java.util.List;
import static org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency;
import static org.hibernate.cfg.AnnotationBinder.matchIgnoreNotFoundWithFetchType;
import static org.hibernate.cfg.BinderHelper.getCascadeStrategy;
import static org.hibernate.cfg.BinderHelper.getFetchMode;
import static org.hibernate.cfg.BinderHelper.getPath;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
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;
/**
* Work in progress
* The goal of this class is to aggregate all operations
* related to ToOne binding operations
*
* @author Emmanuel Bernard
*/
public class ToOneBinder {
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext buildingContext) {
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) {
private static final CoreMessageLogger LOG = messageLogger( ToOneBinder.class );
static void bindManyToOne(
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean isIdentifierMapper,
boolean inSecondPass,
MetadataBuildingContext context,
XProperty property,
AnnotatedJoinColumn[] joinColumns,
PropertyBinder propertyBinder,
boolean forcePersist) {
final ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
//check validity
if ( property.isAnnotationPresent( Column.class )
|| property.isAnnotationPresent( Columns.class ) ) {
throw new AnnotationException(
"Property '"+ getPath( propertyHolder, inferredData )
+ "' is a '@ManyToOne' association and may not use '@Column' to specify column mappings (use '@JoinColumn' instead)"
);
}
final Cascade hibernateCascade = property.getAnnotation( Cascade.class );
final NotFound notFound = property.getAnnotation( NotFound.class );
final NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, manyToOne.fetch() );
final OnDelete onDelete = property.getAnnotation( OnDelete.class );
final JoinTable joinTable = propertyHolder.getJoinTable( property );
if ( joinTable != null ) {
final Join join = propertyHolder.addJoin( joinTable, false );
for ( AnnotatedJoinColumn joinColumn : joinColumns ) {
joinColumn.setExplicitTableName( join.getTable().getName() );
}
}
final boolean mandatory = isMandatory( manyToOne.optional(), property, notFoundAction );
bindManyToOne(
getCascadeStrategy( manyToOne.cascade(), hibernateCascade, false, forcePersist ),
joinColumns,
!mandatory,
notFoundAction,
onDelete != null && OnDeleteAction.CASCADE == onDelete.action(),
getTargetEntity( inferredData, context ),
propertyHolder,
inferredData,
false,
isIdentifierMapper,
inSecondPass,
propertyBinder,
context
);
}
private static boolean isMandatory(boolean optional, XProperty property, NotFoundAction notFoundAction) {
// @MapsId means the columns belong to the pk;
// A @MapsId association (obviously) must be non-null when the entity is first persisted.
// If a @MapsId association is not mapped with @NotFound(IGNORE), then the association
// is mandatory (even if the association has optional=true).
// If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then
// the association is optional.
// @OneToOne(optional = true) with @PKJC makes the association optional.
return !optional
|| property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( MapsId.class ) && notFoundAction != NotFoundAction.IGNORE;
}
private static void bindManyToOne(
String cascadeStrategy,
AnnotatedJoinColumn[] columns,
boolean optional,
NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean unique, // identifies a "logical" @OneToOne
boolean isIdentifierMapper,
boolean inSecondPass,
PropertyBinder propertyBinder,
MetadataBuildingContext context) {
// All FK columns should be in the same table
final org.hibernate.mapping.ManyToOne value =
new org.hibernate.mapping.ManyToOne( context, columns[0].getTable() );
if ( unique ) {
// This is a @OneToOne mapped to a physical o.h.mapping.ManyToOne
value.markAsLogicalOneToOne();
}
value.setReferencedEntityName( getReferenceEntityName( inferredData, targetEntity, context ) );
final XProperty property = inferredData.getProperty();
defineFetchingStrategy( value, property );
//value.setFetchMode( fetchMode );
value.setNotFoundAction( notFoundAction );
value.setCascadeDeleteEnabled( cascadeOnDelete );
//value.setLazy( fetchMode != FetchMode.JOIN );
if ( !optional ) {
for ( AnnotatedJoinColumn column : columns ) {
column.setNullable( false );
}
}
if ( property.isAnnotationPresent( MapsId.class ) ) {
//read only
for ( AnnotatedJoinColumn column : columns ) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
boolean hasSpecjManyToOne = handleSpecjSyntax( columns, inferredData, context, property );
value.setTypeName( inferredData.getClassOrElementName() );
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
final String fullPath = qualify( propertyHolder.getPath(), propertyName );
bindForeignKeyNameAndDefinition( value, property, propertyHolder.getOverriddenForeignKey( fullPath ), context );
final FkSecondPass secondPass = new ToOneFkSecondPass(
value,
columns,
!optional && unique, //cannot have nullable and unique on certain DBs like Derby
propertyHolder.getPersistentClass(),
fullPath,
context
);
if ( inSecondPass ) {
secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() );
}
else {
context.getMetadataCollector().addSecondPass( secondPass );
}
processManyToOneProperty(
cascadeStrategy,
columns,
optional,
propertyHolder,
inferredData,
isIdentifierMapper,
propertyBinder,
value,
property,
hasSpecjManyToOne,
propertyName
);
}
private static boolean handleSpecjSyntax(
AnnotatedJoinColumn[] columns,
PropertyData inferredData,
MetadataBuildingContext context,
XProperty property) {
//Make sure that JPA1 key-many-to-one columns are read only too
boolean hasSpecjManyToOne = false;
if ( context.getBuildingOptions().isSpecjProprietarySyntaxEnabled() ) {
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
String columnName = "";
for ( XProperty prop : inferredData.getDeclaringClass()
.getDeclaredProperties( AccessType.FIELD.getType() ) ) {
if ( prop.isAnnotationPresent( Id.class ) && prop.isAnnotationPresent( Column.class ) ) {
columnName = prop.getAnnotation( Column.class ).name();
}
if ( property.isAnnotationPresent( ManyToOne.class ) && joinColumn != null
&& ! isEmptyAnnotationValue( joinColumn.name() )
&& joinColumn.name().equals( columnName )
&& !property.isAnnotationPresent( MapsId.class ) ) {
hasSpecjManyToOne = true;
for ( AnnotatedJoinColumn column : columns) {
column.setInsertable( false );
column.setUpdatable( false );
}
}
}
}
return hasSpecjManyToOne;
}
private static void processManyToOneProperty(
String cascadeStrategy,
AnnotatedJoinColumn[] columns,
boolean optional,
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean isIdentifierMapper,
PropertyBinder propertyBinder,
org.hibernate.mapping.ManyToOne value, XProperty property,
boolean hasSpecjManyToOne,
String propertyName) {
checkPropertyConsistency( columns, qualify( propertyHolder.getEntityName(), propertyName ) );
//PropertyBinder binder = new PropertyBinder();
propertyBinder.setName( propertyName );
propertyBinder.setValue( value );
//binder.setCascade(cascadeStrategy);
if (isIdentifierMapper) {
propertyBinder.setInsertable( false );
propertyBinder.setUpdatable( false );
}
else if (hasSpecjManyToOne) {
propertyBinder.setInsertable( false );
propertyBinder.setUpdatable( false );
}
else {
propertyBinder.setInsertable( columns[0].isInsertable() );
propertyBinder.setUpdatable( columns[0].isUpdatable() );
}
propertyBinder.setColumns( columns );
propertyBinder.setAccessType( inferredData.getDefaultAccess() );
propertyBinder.setCascade( cascadeStrategy );
propertyBinder.setProperty( property );
propertyBinder.setXToMany( true );
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
propertyBinder.makePropertyAndBind()
.setOptional( optional && isNullable( joinColumns, joinColumn ) );
}
private static boolean isNullable(JoinColumns joinColumns, JoinColumn joinColumn) {
if ( joinColumn != null ) {
return joinColumn.nullable();
}
else if ( joinColumns != null ) {
for ( JoinColumn column : joinColumns.value() ) {
if ( column.nullable() ) {
return true;
}
}
return false;
}
else {
return true;
}
}
static void defineFetchingStrategy(ToOne toOne, XProperty property) {
final FetchType fetchType = getJpaFetchType( property );
final LazyToOne lazy = property.getAnnotation( LazyToOne.class );
final NotFound notFound = property.getAnnotation( NotFound.class );
if ( notFound != null ) {
toOne.setLazy( false );
toOne.setUnwrapProxy( true );
}
else if ( lazy != null ) {
toOne.setLazy( lazy.value() != LazyToOneOption.FALSE );
toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
}
else {
toOne.setLazy( fetchType == FetchType.LAZY );
toOne.setUnwrapProxy( fetchType != FetchType.LAZY );
toOne.setUnwrapProxyImplicit( true );
}
final Fetch fetch = property.getAnnotation( Fetch.class );
if ( fetch != null ) {
// Hibernate @Fetch annotation takes precedence
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
toOne.setFetchMode( FetchMode.JOIN );
toOne.setLazy( false );
toOne.setUnwrapProxy( false );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
toOne.setFetchMode( FetchMode.SELECT );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
throw new AnnotationException( "Association '" + property.getName()
+ "' is annotated '@Fetch(SUBSELECT)' but is not many-valued");
}
else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
}
}
else {
toOne.setFetchMode( getFetchMode( fetchType ) );
}
}
private static FetchType getJpaFetchType(XProperty property) {
final ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
final OneToOne oneToOne = property.getAnnotation( OneToOne.class );
if ( manyToOne != null ) {
return manyToOne.fetch();
}
else if ( oneToOne != null ) {
return oneToOne.fetch();
}
else {
throw new AssertionFailure("Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne");
}
}
static void bindOneToOne(
PropertyHolder propertyHolder,
PropertyData inferredData,
boolean isIdentifierMapper,
boolean inSecondPass,
MetadataBuildingContext context,
XProperty property,
AnnotatedJoinColumn[] joinColumns,
PropertyBinder propertyBinder,
boolean forcePersist) {
final OneToOne oneToOne = property.getAnnotation( OneToOne.class );
//check validity
if ( property.isAnnotationPresent( Column.class )
|| property.isAnnotationPresent( Columns.class ) ) {
throw new AnnotationException(
"Property '"+ getPath( propertyHolder, inferredData )
+ "' is a '@OneToOne' association and may not use '@Column' to specify column mappings"
+ " (use '@PrimaryKeyJoinColumn' instead)"
);
}
//FIXME support a proper PKJCs
final boolean trueOneToOne = property.isAnnotationPresent( PrimaryKeyJoinColumn.class )
|| property.isAnnotationPresent( PrimaryKeyJoinColumns.class );
final Cascade hibernateCascade = property.getAnnotation( Cascade.class );
final NotFound notFound = property.getAnnotation( NotFound.class );
final NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
final boolean mandatory = isMandatory( oneToOne.optional(), property, notFoundAction );
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, oneToOne.fetch() );
final OnDelete onDelete = property.getAnnotation( OnDelete.class );
final JoinTable joinTable = propertyHolder.getJoinTable(property);
if ( joinTable != null ) {
final Join join = propertyHolder.addJoin( joinTable, false );
if ( notFoundAction != null ) {
join.disableForeignKeyCreation();
}
for ( AnnotatedJoinColumn joinColumn : joinColumns) {
joinColumn.setExplicitTableName( join.getTable().getName() );
}
}
bindOneToOne(
getCascadeStrategy( oneToOne.cascade(), hibernateCascade, oneToOne.orphanRemoval(), forcePersist ),
joinColumns,
!mandatory,
getFetchMode( oneToOne.fetch() ),
notFoundAction,
onDelete != null && OnDeleteAction.CASCADE == onDelete.action(),
getTargetEntity( inferredData, context ),
propertyHolder,
inferredData,
oneToOne.mappedBy(),
trueOneToOne,
isIdentifierMapper,
inSecondPass,
propertyBinder,
context
);
}
private static void bindOneToOne(
String cascadeStrategy,
AnnotatedJoinColumn[] joinColumns,
boolean optional,
FetchMode fetchMode,
NotFoundAction notFoundAction,
boolean cascadeOnDelete,
XClass targetEntity,
PropertyHolder propertyHolder,
PropertyData inferredData,
String mappedBy,
boolean trueOneToOne,
boolean isIdentifierMapper,
boolean inSecondPass,
PropertyBinder propertyBinder,
MetadataBuildingContext context) {
//column.getTable() => persistentClass.getTable()
final String propertyName = inferredData.getPropertyName();
LOG.tracev( "Fetching {0} with {1}", propertyName, fetchMode );
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne ) || !isEmptyAnnotationValue( mappedBy ) ) {
//is a true one-to-one
//FIXME referencedColumnName ignored => ordering may fail.
final OneToOneSecondPass secondPass = new OneToOneSecondPass(
mappedBy,
propertyHolder.getEntityName(),
propertyName,
propertyHolder,
inferredData,
targetEntity,
notFoundAction,
cascadeOnDelete,
optional,
cascadeStrategy,
joinColumns,
context
);
if ( inSecondPass ) {
secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() );
}
else {
context.getMetadataCollector().addSecondPass( secondPass, isEmptyAnnotationValue( mappedBy ) );
}
}
else {
//has a FK on the table
bindManyToOne(
cascadeStrategy,
joinColumns,
optional,
notFoundAction,
cascadeOnDelete,
targetEntity,
propertyHolder,
inferredData,
true,
isIdentifierMapper,
inSecondPass,
propertyBinder,
context
);
}
}
private static boolean isMapToPK(AnnotatedJoinColumn[] joinColumns, PropertyHolder propertyHolder, boolean trueOneToOne) {
if ( trueOneToOne ) {
return true;
}
else {
//try to find a hidden true one to one (FK == PK columns)
KeyValue identifier = propertyHolder.getIdentifier();
if ( identifier == null ) {
//this is a @OneToOne in an @EmbeddedId (the persistentClass.identifier is not set yet, it's being built)
//by definition the PK cannot refer to itself so it cannot map to itself
return false;
}
else {
List<String> idColumnNames = new ArrayList<>();
if ( identifier.getColumnSpan() != joinColumns.length ) {
return false;
}
else {
for ( org.hibernate.mapping.Column currentColumn: identifier.getColumns() ) {
idColumnNames.add( currentColumn.getName() );
}
for ( AnnotatedJoinColumn col: joinColumns) {
if ( !idColumnNames.contains( col.getMappingColumn().getName() ) ) {
return false;
}
}
return true;
}
}
}
}
public static void bindForeignKeyNameAndDefinition(
SimpleValue value,
XProperty property,
ForeignKey foreignKey,
MetadataBuildingContext context) {
if ( property.getAnnotation( NotFound.class ) != null ) {
// supersedes all others
value.disableForeignKey();
}
else {
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
if ( joinColumn!=null && noConstraint( joinColumn.foreignKey(), context )
|| joinColumns!=null && noConstraint( joinColumns.foreignKey(), context ) ) {
value.disableForeignKey();
}
else {
final org.hibernate.annotations.ForeignKey fk =
property.getAnnotation( org.hibernate.annotations.ForeignKey.class );
if ( fk != null && isNotEmpty( fk.name() ) ) {
value.setForeignKeyName( fk.name() );
}
else {
if ( noConstraint( foreignKey, context ) ) {
value.disableForeignKey();
}
else if ( foreignKey != null ) {
value.setForeignKeyName( nullIfEmpty( foreignKey.name() ) );
value.setForeignKeyDefinition( nullIfEmpty( foreignKey.foreignKeyDefinition() ) );
}
else if ( joinColumns != null ) {
value.setForeignKeyName( nullIfEmpty( joinColumns.foreignKey().name() ) );
value.setForeignKeyDefinition( nullIfEmpty( joinColumns.foreignKey().foreignKeyDefinition() ) );
}
else if ( joinColumn != null ) {
value.setForeignKeyName( nullIfEmpty( joinColumn.foreignKey().name() ) );
value.setForeignKeyDefinition( nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
}
}
}
}
}
private static boolean noConstraint(ForeignKey joinColumns, MetadataBuildingContext context) {
return joinColumns != null
&& ( joinColumns.value() == ConstraintMode.NO_CONSTRAINT
|| joinColumns.value() == ConstraintMode.PROVIDER_DEFAULT
&& context.getBuildingOptions().isNoConstraintByDefault() );
}
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
if ( AnnotationBinder.isDefault( targetEntity, context ) ) {
return propertyData.getClassOrElementName();
}
else {
@ -31,19 +569,16 @@ public class ToOneBinder {
}
}
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext buildingContext) {
XClass targetEntity = getTargetEntity( propertyData, buildingContext );
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) {
return propertyData.getClassOrElementName();
}
else {
return targetEntity.getName();
}
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) {
XClass targetEntity = getTargetEntity( propertyData, context );
return AnnotationBinder.isDefault( targetEntity, context )
? propertyData.getClassOrElementName()
: targetEntity.getName();
}
public static XClass getTargetEntity(PropertyData propertyData, MetadataBuildingContext buildingContext) {
XProperty property = propertyData.getProperty();
return buildingContext.getBootstrapContext().getReflectionManager().toXClass( getTargetEntityClass( property ) );
public static XClass getTargetEntity(PropertyData propertyData, MetadataBuildingContext context) {
return context.getBootstrapContext().getReflectionManager()
.toXClass( getTargetEntityClass( propertyData.getProperty() ) );
}
private static Class<?> getTargetEntityClass(XProperty property) {

View File

@ -155,10 +155,11 @@ import static org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency;
import static org.hibernate.cfg.AnnotatedJoinColumn.buildJoinColumnsWithDefaultColumnSuffix;
import static org.hibernate.cfg.AnnotatedJoinColumn.buildJoinTableJoinColumns;
import static org.hibernate.cfg.AnnotationBinder.fillComponent;
import static org.hibernate.cfg.AnnotationBinder.getCascadeStrategy;
import static org.hibernate.cfg.BinderHelper.PRIMITIVE_NAMES;
import static org.hibernate.cfg.BinderHelper.buildAnyValue;
import static org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference;
import static org.hibernate.cfg.BinderHelper.getCascadeStrategy;
import static org.hibernate.cfg.BinderHelper.getFetchMode;
import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation;
import static org.hibernate.cfg.BinderHelper.getPath;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
@ -1416,7 +1417,7 @@ public abstract class CollectionBinder {
}
}
else {
collection.setFetchMode( AnnotationBinder.getFetchMode( jpaFetchType ) );
collection.setFetchMode( getFetchMode( jpaFetchType ) );
}
}

View File

@ -10,14 +10,10 @@ import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.MappingException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.CollectionSecondPass;
import org.hibernate.cfg.AnnotatedColumn;
import org.hibernate.cfg.AnnotatedJoinColumn;
import org.hibernate.cfg.PropertyHolder;
import org.hibernate.cfg.PropertyHolderBuilder;
import org.hibernate.cfg.SecondPass;
@ -69,12 +65,12 @@ public class ListBinder extends CollectionBinder {
public void secondPass(Map<String, PersistentClass> persistentClasses)
throws MappingException {
bindStarToManySecondPass( persistentClasses );
bindIndex( property, getElementType(), buildingContext );
bindIndex();
}
};
}
private void bindIndex(XProperty property, XClass elementType, final MetadataBuildingContext buildingContext) {
private void bindIndex() {
final PropertyHolder valueHolder = PropertyHolderBuilder.buildPropertyHolder(
collection,
qualify( collection.getRole(), "key" ),
@ -93,23 +89,23 @@ public class ListBinder extends CollectionBinder {
final BasicValueBinder valueBinder = new BasicValueBinder( BasicValueBinder.Kind.LIST_INDEX, buildingContext );
valueBinder.setColumns( new AnnotatedColumn[] { indexColumn } );
valueBinder.setReturnedClassName( Integer.class.getName() );
valueBinder.setType( property, elementType, null, null );
valueBinder.setType( property, getElementType(), null, null );
// valueBinder.setExplicitType( "integer" );
SimpleValue indexValue = valueBinder.make();
final SimpleValue indexValue = valueBinder.make();
indexColumn.linkWithValue( indexValue );
listValueMapping.setIndex( indexValue );
listValueMapping.setBaseIndex( indexColumn.getBase() );
if ( listValueMapping.isOneToMany() && !listValueMapping.getKey().isNullable() && !listValueMapping.isInverse() ) {
String entityName = ( (OneToMany) listValueMapping.getElement() ).getReferencedEntityName();
PersistentClass referenced = buildingContext.getMetadataCollector().getEntityBinding( entityName );
IndexBackref ib = new IndexBackref();
ib.setName( '_' + propertyName + "IndexBackref" );
ib.setUpdateable( false );
ib.setSelectable( false );
ib.setCollectionRole( listValueMapping.getRole() );
ib.setEntityName( listValueMapping.getOwner().getEntityName() );
ib.setValue( listValueMapping.getIndex() );
referenced.addProperty( ib );
final String entityName = ( (OneToMany) listValueMapping.getElement() ).getReferencedEntityName();
final PersistentClass referenced = buildingContext.getMetadataCollector().getEntityBinding( entityName );
final IndexBackref backref = new IndexBackref();
backref.setName( '_' + propertyName + "IndexBackref" );
backref.setUpdateable( false );
backref.setSelectable( false );
backref.setCollectionRole( listValueMapping.getRole() );
backref.setEntityName( listValueMapping.getOwner().getEntityName() );
backref.setValue( listValueMapping.getIndex() );
referenced.addProperty( backref );
}
}
}