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:
parent
8b3030aa8b
commit
ed65962fb3
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,108 +85,93 @@ 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 {
|
||||
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 );
|
||||
PersistentClass otherSide = persistentClasses.get( value.getReferencedEntityName() );
|
||||
if ( otherSide == null ) {
|
||||
final PersistentClass targetEntity = persistentClasses.get( value.getReferencedEntityName() );
|
||||
if ( targetEntity == 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 ) {
|
||||
final Property targetProperty = targetProperty( value, targetEntity );
|
||||
if ( targetProperty.getValue() instanceof OneToOne ) {
|
||||
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
|
||||
}
|
||||
else if ( otherSideProperty.getValue() instanceof ManyToOne ) {
|
||||
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 : otherSide.getJoins() ) {
|
||||
if ( otherSideJoinValue.containsProperty( otherSideProperty ) ) {
|
||||
for ( Join otherSideJoinValue : targetEntity.getJoins() ) {
|
||||
if ( otherSideJoinValue.containsProperty(targetProperty) ) {
|
||||
otherSideJoin = otherSideJoinValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( otherSideJoin != null ) {
|
||||
//@OneToOne @JoinTable
|
||||
Join mappedByJoin = buildJoinFromMappedBySide(
|
||||
persistentClasses.get( ownerEntity ), otherSideProperty, otherSideJoin
|
||||
final Join mappedByJoin = buildJoinFromMappedBySide(
|
||||
persistentClasses.get( ownerEntity ), targetProperty, otherSideJoin
|
||||
);
|
||||
ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
|
||||
final ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
|
||||
//FIXME use ignore not found here
|
||||
manyToOne.setNotFoundAction( notFoundAction );
|
||||
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
|
||||
|
@ -220,27 +208,57 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
// 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
|
||||
|| otherSide.getIdentifier() instanceof Component
|
||||
&& ! ( (Component) otherSide.getIdentifier() ).hasProperty( mappedBy );
|
||||
|| targetEntityIdentifier instanceof Component
|
||||
&& !( (Component) targetEntityIdentifier ).hasProperty( mappedBy );
|
||||
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
|
||||
|
||||
String propertyRef = value.getReferencedPropertyName();
|
||||
final String propertyRef = value.getReferencedPropertyName();
|
||||
if ( propertyRef != null ) {
|
||||
buildingContext.getMetadataCollector().addUniquePropertyReference(
|
||||
value.getReferencedEntityName(),
|
||||
propertyRef
|
||||
);
|
||||
buildingContext.getMetadataCollector()
|
||||
.addUniquePropertyReference( value.getReferencedEntityName(), propertyRef );
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
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
|
||||
+ "' of the target entity type '" + value.getReferencedEntityName()
|
||||
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
|
||||
+ "' 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() );
|
||||
}
|
||||
value.sortProperties();
|
||||
// else {
|
||||
// this is a @ManyToOne with Formula
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue