Skip to content

Commit 3e691d2

Browse files
committed
HHH-19739 Construct property for OneToOne early to allow field deduplication
1 parent 8d9ce0d commit 3e691d2

File tree

8 files changed

+123
-84
lines changed

8 files changed

+123
-84
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ public void addProperty(Property prop, MemberDetails memberDetails, ClassDetails
180180
}
181181
}
182182

183+
@Override
184+
public void movePropertyToJoin(Property property, Join join, MemberDetails memberDetails, ClassDetails declaringClass) {
185+
if ( property.getValue() instanceof Component ) {
186+
//TODO handle quote and non quote table comparison
187+
final String tableName = property.getValue().getTable().getName();
188+
if ( getJoinsPerRealTableName().get( tableName ) == join ) {
189+
// Skip moving the property, since it was already added to the join
190+
return;
191+
}
192+
}
193+
persistentClass.movePropertyToJoin( property, join );
194+
}
195+
183196
@Override
184197
public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) {
185198
final var join = entityBinder.addJoinTable( joinTableAnn, this, noDelayInPkColumnCreation );

hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionPropertyHolder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ public void addProperty(Property prop, MemberDetails memberDetails, ClassDetails
264264
throw new AssertionFailure( "Cannot add property to a collection" );
265265
}
266266

267+
@Override
268+
public void movePropertyToJoin(Property prop, Join join, MemberDetails memberDetails, ClassDetails declaringClass) {
269+
throw new AssertionFailure( "Cannot add property to a collection" );
270+
}
271+
267272
@Override
268273
public KeyValue getIdentifier() {
269274
throw new AssertionFailure( "Identifier collection not yet managed" );

hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,16 @@ public void addProperty(Property prop, MemberDetails attributeMemberDetails, Cla
355355
component.addProperty( prop, declaringClass );
356356
}
357357

358+
@Override
359+
public void movePropertyToJoin(Property prop, Join join, MemberDetails memberDetails, ClassDetails declaringClass) {
360+
// or maybe only throw if component.getTable() != join.getTable()
361+
throw new AnnotationException(
362+
"Embeddable class '" + component.getComponentClassName()
363+
+ "' has an unowned @OneToOne property " + prop.getName()
364+
+ "mapped to a join table which is unsupported"
365+
);
366+
}
367+
358368
@Override
359369
public KeyValue getIdentifier() {
360370
return component.getOwner().getIdentifier();

hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java

Lines changed: 20 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44
*/
55
package org.hibernate.boot.model.internal;
66

7-
import java.util.EnumSet;
87
import java.util.Map;
98

109
import org.hibernate.AnnotationException;
1110
import org.hibernate.MappingException;
12-
import org.hibernate.annotations.CascadeType;
13-
import org.hibernate.annotations.LazyGroup;
1411
import org.hibernate.annotations.NotFoundAction;
15-
import org.hibernate.annotations.OnDeleteAction;
1612
import org.hibernate.boot.spi.MetadataBuildingContext;
1713
import org.hibernate.boot.spi.PropertyData;
1814
import org.hibernate.boot.spi.SecondPass;
@@ -24,19 +20,11 @@
2420
import org.hibernate.mapping.PersistentClass;
2521
import org.hibernate.mapping.Property;
2622
import org.hibernate.mapping.SortableValue;
27-
import org.hibernate.models.spi.MemberDetails;
28-
import org.hibernate.type.ForeignKeyDirection;
29-
30-
import jakarta.persistence.ForeignKey;
3123

3224
import static org.hibernate.boot.model.internal.BinderHelper.checkMappedByType;
3325
import static org.hibernate.boot.model.internal.BinderHelper.findPropertyByName;
3426
import static org.hibernate.boot.model.internal.BinderHelper.getPath;
35-
import static org.hibernate.boot.model.internal.ToOneBinder.bindForeignKeyNameAndDefinition;
36-
import static org.hibernate.boot.model.internal.ToOneBinder.defineFetchingStrategy;
3727
import static org.hibernate.internal.util.StringHelper.qualify;
38-
import static org.hibernate.type.ForeignKeyDirection.FROM_PARENT;
39-
import static org.hibernate.type.ForeignKeyDirection.TO_PARENT;
4028

4129
/**
4230
* We have to handle {@link jakarta.persistence.OneToOne} associations
@@ -45,95 +33,54 @@
4533
public class OneToOneSecondPass implements SecondPass {
4634
private final PropertyData inferredData;
4735
private final PropertyHolder propertyHolder;
36+
private final OneToOne oneToOne;
37+
private final PropertyBinder binder;
38+
private final Property property;
4839
private final String mappedBy;
4940
private final String ownerEntity;
5041
private final NotFoundAction notFoundAction;
51-
private final OnDeleteAction onDeleteAction;
52-
private final boolean optional;
53-
private final EnumSet<CascadeType> cascadeStrategy;
5442
private final AnnotatedJoinColumns joinColumns;
5543
private final MetadataBuildingContext buildingContext;
56-
private final String referencedEntityName;
5744
private final boolean annotatedEntity;
5845

5946
public OneToOneSecondPass(
47+
PropertyBinder binder,
48+
Property property,
49+
OneToOne oneToOne,
6050
String mappedBy,
6151
String ownerEntity,
6252
PropertyHolder propertyHolder,
6353
PropertyData inferredData,
64-
String referencedEntityName,
6554
boolean annotatedEntity,
6655
NotFoundAction notFoundAction,
67-
OnDeleteAction onDeleteAction,
68-
boolean optional,
69-
EnumSet<CascadeType> cascadeStrategy,
7056
AnnotatedJoinColumns columns,
7157
MetadataBuildingContext buildingContext) {
58+
this.binder = binder;
59+
this.property = property;
60+
this.oneToOne = oneToOne;
7261
this.ownerEntity = ownerEntity;
7362
this.mappedBy = mappedBy;
7463
this.propertyHolder = propertyHolder;
75-
this.referencedEntityName = referencedEntityName;
7664
this.buildingContext = buildingContext;
7765
this.notFoundAction = notFoundAction;
7866
this.inferredData = inferredData;
7967
this.annotatedEntity = annotatedEntity;
80-
this.onDeleteAction = onDeleteAction;
81-
this.optional = optional;
82-
this.cascadeStrategy = cascadeStrategy;
8368
this.joinColumns = columns;
8469
}
8570

8671
@Override
8772
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
88-
final var oneToOne =
89-
new OneToOne( buildingContext, propertyHolder.getTable(),
90-
propertyHolder.getPersistentClass() );
91-
final String propertyName = inferredData.getPropertyName();
92-
oneToOne.setPropertyName( propertyName );
93-
oneToOne.setReferencedEntityName( referencedEntityName );
94-
MemberDetails property = inferredData.getAttributeMember();
95-
defineFetchingStrategy( oneToOne, property, inferredData, propertyHolder );
96-
//value.setFetchMode( fetchMode );
97-
oneToOne.setOnDeleteAction( onDeleteAction );
98-
//value.setLazy( fetchMode != FetchMode.JOIN );
99-
100-
oneToOne.setConstrained( !optional );
101-
oneToOne.setForeignKeyType( getForeignKeyDirection() );
102-
bindForeignKeyNameAndDefinition( oneToOne, property,
103-
property.getDirectAnnotationUsage( ForeignKey.class ),
104-
buildingContext );
105-
106-
final var binder = new PropertyBinder();
107-
binder.setName( propertyName );
108-
binder.setMemberDetails( property );
109-
binder.setValue( oneToOne );
110-
binder.setCascade( cascadeStrategy );
111-
binder.setAccessType( inferredData.getDefaultAccess() );
112-
binder.setBuildingContext( buildingContext );
113-
binder.setHolder( propertyHolder );
114-
115-
final var lazyGroupAnnotation = property.getDirectAnnotationUsage( LazyGroup.class );
116-
if ( lazyGroupAnnotation != null ) {
117-
binder.setLazyGroup( lazyGroupAnnotation.value() );
118-
}
119-
120-
final Property result = binder.makeProperty();
121-
result.setOptional( optional );
12273
if ( mappedBy == null ) {
123-
bindOwned( persistentClasses, oneToOne, propertyName, result );
74+
bindOwned( persistentClasses, oneToOne, inferredData.getPropertyName() );
12475
}
12576
else {
126-
bindUnowned( persistentClasses, oneToOne, result );
77+
bindUnowned( persistentClasses, oneToOne );
12778
}
128-
binder.callAttributeBindersInSecondPass( result );
79+
binder.callAttributeBindersInSecondPass( property );
12980
oneToOne.sortProperties();
13081
}
13182

132-
private ForeignKeyDirection getForeignKeyDirection() {
133-
return mappedBy == null ? FROM_PARENT : TO_PARENT;
134-
}
135-
136-
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, Property property) {
83+
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne) {
13784
oneToOne.setMappedByProperty( mappedBy );
13885
final String targetEntityName = oneToOne.getReferencedEntityName();
13986
final var targetEntity = persistentClasses.get( targetEntityName );
@@ -146,13 +93,10 @@ private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOn
14693
}
14794
final var targetProperty = targetProperty( oneToOne, targetEntity );
14895
final var targetPropertyValue = targetProperty.getValue();
149-
if ( targetPropertyValue instanceof OneToOne ) {
150-
propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() );
96+
if ( targetPropertyValue instanceof ManyToOne ) {
97+
bindTargetManyToOne( persistentClasses, oneToOne, targetEntity, targetProperty );
15198
}
152-
else if ( targetPropertyValue instanceof ManyToOne ) {
153-
bindTargetManyToOne( persistentClasses, oneToOne, property, targetEntity, targetProperty );
154-
}
155-
else {
99+
else if ( !(targetPropertyValue instanceof OneToOne) ) {
156100
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
157101
+ "' is 'mappedBy' a property named '" + mappedBy
158102
+ "' of the target entity type '" + targetEntityName
@@ -170,7 +114,6 @@ else if ( targetPropertyValue instanceof ManyToOne ) {
170114
private void bindTargetManyToOne(
171115
Map<String, PersistentClass> persistentClasses,
172116
OneToOne oneToOne,
173-
Property property,
174117
PersistentClass targetEntity,
175118
Property targetProperty) {
176119
Join otherSideJoin = null;
@@ -192,10 +135,9 @@ private void bindTargetManyToOne(
192135
copy.setValue( manyToOne );
193136
manyToOne.addColumn( copy );
194137
}
195-
mappedByJoin.addProperty( property );
196-
}
197-
else {
198-
propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() );
138+
// The property was added to the propertyHolder eagerly to have knowledge about this property,
139+
// in order for de-duplication to kick in, but we move it to a join if necessary
140+
propertyHolder.movePropertyToJoin( property, mappedByJoin, inferredData.getAttributeMember(), inferredData.getDeclaringClass() );
199141
}
200142

201143
oneToOne.setReferencedPropertyName( mappedBy );
@@ -246,8 +188,7 @@ private Property targetProperty(OneToOne oneToOne, PersistentClass targetEntity)
246188
private void bindOwned(
247189
Map<String, PersistentClass> persistentClasses,
248190
OneToOne oneToOne,
249-
String propertyName,
250-
Property property) {
191+
String propertyName) {
251192
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
252193
oneToOne,
253194
joinColumns,
@@ -259,7 +200,6 @@ private void bindOwned(
259200
);
260201
secondPass.doSecondPass(persistentClasses);
261202
//no column associated since it's a one to one
262-
propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() );
263203
}
264204

265205
/**

hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyHolder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public interface PropertyHolder {
3636

3737
void addProperty(Property prop, MemberDetails memberDetails, @Nullable AnnotatedColumns columns, ClassDetails declaringClass);
3838

39+
void movePropertyToJoin(Property prop, Join join, MemberDetails memberDetails, ClassDetails declaringClass);
40+
3941
KeyValue getIdentifier();
4042

4143
/**

hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.annotations.Columns;
1515
import org.hibernate.annotations.Fetch;
1616
import org.hibernate.annotations.FetchProfileOverride;
17+
import org.hibernate.annotations.LazyGroup;
1718
import org.hibernate.annotations.NotFound;
1819
import org.hibernate.annotations.NotFoundAction;
1920
import org.hibernate.annotations.OnDelete;
@@ -22,6 +23,7 @@
2223
import org.hibernate.boot.spi.PropertyData;
2324
import org.hibernate.mapping.Join;
2425
import org.hibernate.mapping.KeyValue;
26+
import org.hibernate.mapping.Property;
2527
import org.hibernate.mapping.SimpleValue;
2628
import org.hibernate.mapping.ToOne;
2729
import org.hibernate.models.spi.ClassDetails;
@@ -52,6 +54,8 @@
5254
import static org.hibernate.internal.util.StringHelper.isBlank;
5355
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
5456
import static org.hibernate.internal.util.StringHelper.qualify;
57+
import static org.hibernate.type.ForeignKeyDirection.FROM_PARENT;
58+
import static org.hibernate.type.ForeignKeyDirection.TO_PARENT;
5559

5660
/**
5761
* Responsible for interpreting {@link ManyToOne} and {@link OneToOne} associations
@@ -521,17 +525,53 @@ private static void bindTrueOneToOne(
521525
final var memberDetails = inferredData.getAttributeMember();
522526
final var targetEntityClassDetails = getTargetEntity( inferredData, context );
523527
final var notFoundAction = notFoundAction( propertyHolder, memberDetails, fetchMode );
528+
final var optional = !isMandatory( explicitlyOptional, memberDetails, notFoundAction );
529+
530+
final var oneToOne =
531+
new org.hibernate.mapping.OneToOne( context, propertyHolder.getTable(),
532+
propertyHolder.getPersistentClass() );
533+
final String propertyName = inferredData.getPropertyName();
534+
oneToOne.setPropertyName( propertyName );
535+
oneToOne.setReferencedEntityName( getReferenceEntityName( inferredData, targetEntityClassDetails ) );
536+
defineFetchingStrategy( oneToOne, memberDetails, inferredData, propertyHolder );
537+
//value.setFetchMode( fetchMode );
538+
oneToOne.setOnDeleteAction( onDeleteAction( memberDetails ) );
539+
//value.setLazy( fetchMode != FetchMode.JOIN );
540+
541+
oneToOne.setConstrained( !optional );
542+
oneToOne.setForeignKeyType( mappedBy == null ? FROM_PARENT : TO_PARENT );
543+
bindForeignKeyNameAndDefinition( oneToOne, memberDetails,
544+
memberDetails.getDirectAnnotationUsage( ForeignKey.class ),
545+
context );
546+
547+
final var binder = new PropertyBinder();
548+
binder.setName( inferredData.getPropertyName() );
549+
binder.setMemberDetails( memberDetails );
550+
binder.setValue( oneToOne );
551+
binder.setCascade( cascadeStrategy );
552+
binder.setAccessType( inferredData.getDefaultAccess() );
553+
binder.setBuildingContext( context );
554+
binder.setHolder( propertyHolder );
555+
556+
final var lazyGroupAnnotation = memberDetails.getDirectAnnotationUsage( LazyGroup.class );
557+
if ( lazyGroupAnnotation != null ) {
558+
binder.setLazyGroup( lazyGroupAnnotation.value() );
559+
}
560+
561+
final Property property = binder.makeProperty();
562+
property.setOptional( optional );
563+
propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() );
564+
524565
final var secondPass = new OneToOneSecondPass(
566+
binder,
567+
property,
568+
oneToOne,
525569
mappedBy,
526570
propertyHolder.getEntityName(),
527571
propertyHolder,
528572
inferredData,
529-
getReferenceEntityName( inferredData, targetEntityClassDetails ),
530573
isTargetAnnotatedEntity( targetEntityClassDetails, memberDetails ),
531574
notFoundAction,
532-
onDeleteAction( memberDetails ),
533-
!isMandatory( explicitlyOptional, memberDetails, notFoundAction ),
534-
cascadeStrategy,
535575
joinColumns,
536576
context
537577
);

hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,20 @@ public void addProperty(Property property) {
281281
property.setPersistentClass( this );
282282
}
283283

284+
@Internal
285+
public void movePropertyToJoin(Property property, Join join) {
286+
assert joins.contains( join );
287+
assert property.getPersistentClass() == this;
288+
properties.remove( property );
289+
declaredProperties.remove( property );
290+
join.addProperty( property );
291+
}
292+
293+
@Internal
294+
protected void moveSubclassPropertyToJoin(Property property) {
295+
subclassProperties.remove( property );
296+
}
297+
284298
@Override
285299
public boolean contains(Property property) {
286300
return properties.contains( property );

hibernate-core/src/main/java/org/hibernate/mapping/Subclass.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.HashSet;
99
import java.util.List;
1010

11+
import org.hibernate.Internal;
1112
import org.hibernate.boot.spi.MetadataBuildingContext;
1213
import org.hibernate.engine.OptimisticLockStyle;
1314
import org.hibernate.internal.FilterConfiguration;
@@ -107,6 +108,20 @@ public void addProperty(Property property) {
107108
getSuperclass().addSubclassProperty( property );
108109
}
109110

111+
@Internal
112+
@Override
113+
public void movePropertyToJoin(Property property, Join join) {
114+
super.movePropertyToJoin( property, join );
115+
getSuperclass().moveSubclassPropertyToJoin( property );
116+
}
117+
118+
@Internal
119+
@Override
120+
protected void moveSubclassPropertyToJoin(Property property) {
121+
super.moveSubclassPropertyToJoin( property );
122+
getSuperclass().moveSubclassPropertyToJoin( property );
123+
}
124+
110125
@Override
111126
public void addMappedSuperclassProperty(Property property) {
112127
super.addMappedSuperclassProperty( property );

0 commit comments

Comments
 (0)