Skip to content

Commit aa88f3c

Browse files
committed
HHH-15739 error for polymorphic lazy @Proxyless
1 parent fa79851 commit aa88f3c

File tree

4 files changed

+68
-43
lines changed

4 files changed

+68
-43
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,31 +131,36 @@ private ForeignKeyDirection getForeignKeyDirection() {
131131
return !isEmptyAnnotationValue( mappedBy ) ? ForeignKeyDirection.TO_PARENT : ForeignKeyDirection.FROM_PARENT;
132132
}
133133

134-
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne value, Property property) {
135-
value.setMappedByProperty( mappedBy );
136-
final PersistentClass targetEntity = persistentClasses.get( value.getReferencedEntityName() );
134+
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, Property property) {
135+
oneToOne.setMappedByProperty( mappedBy );
136+
final PersistentClass targetEntity = persistentClasses.get( oneToOne.getReferencedEntityName() );
137137
if ( targetEntity == null ) {
138138
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
139-
+ "' targets unknown entity type '" + value.getReferencedEntityName() + "'" );
139+
+ "' targets unknown entity type '" + oneToOne.getReferencedEntityName() + "'" );
140140
}
141-
final Property targetProperty = targetProperty( value, targetEntity );
141+
if ( !oneToOne.isLazyPolymorphismAllowed() && oneToOne.isLazy() && targetEntity.hasSubclasses() ) {
142+
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
143+
+ "' is annotated '@Proxyless' but targets an entity named '"
144+
+ oneToOne.getReferencedEntityName() + "' which has subclasses" );
145+
}
146+
final Property targetProperty = targetProperty( oneToOne, targetEntity );
142147
if ( targetProperty.getValue() instanceof OneToOne ) {
143148
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
144149
}
145150
else if ( targetProperty.getValue() instanceof ManyToOne ) {
146-
bindTargetManyToOne( persistentClasses, value, property, targetEntity, targetProperty );
151+
bindTargetManyToOne( persistentClasses, oneToOne, property, targetEntity, targetProperty );
147152
}
148153
else {
149154
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
150155
+ "' is 'mappedBy' a property named '" + mappedBy
151-
+ "' of the target entity type '" + value.getReferencedEntityName()
156+
+ "' of the target entity type '" + oneToOne.getReferencedEntityName()
152157
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
153158
}
154159
}
155160

156161
private void bindTargetManyToOne(
157162
Map<String, PersistentClass> persistentClasses,
158-
OneToOne value,
163+
OneToOne oneToOne,
159164
Property property,
160165
PersistentClass targetEntity,
161166
Property targetProperty) {
@@ -174,11 +179,11 @@ private void bindTargetManyToOne(
174179
final ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
175180
//FIXME use ignore not found here
176181
manyToOne.setNotFoundAction( notFoundAction );
177-
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
178-
manyToOne.setFetchMode( value.getFetchMode() );
179-
manyToOne.setLazy( value.isLazy() );
180-
manyToOne.setReferencedEntityName( value.getReferencedEntityName() );
181-
manyToOne.setUnwrapProxy( value.isUnwrapProxy() );
182+
manyToOne.setCascadeDeleteEnabled( oneToOne.isCascadeDeleteEnabled() );
183+
manyToOne.setFetchMode( oneToOne.getFetchMode() );
184+
manyToOne.setLazy( oneToOne.isLazy() );
185+
manyToOne.setReferencedEntityName( oneToOne.getReferencedEntityName() );
186+
manyToOne.setUnwrapProxy( oneToOne.isUnwrapProxy() );
182187
manyToOne.markAsLogicalOneToOne();
183188
property.setValue( manyToOne );
184189
for ( Column column: otherSideJoin.getKey().getColumns() ) {
@@ -203,7 +208,7 @@ private void bindTargetManyToOne(
203208
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
204209
}
205210

206-
value.setReferencedPropertyName( mappedBy );
211+
oneToOne.setReferencedPropertyName( mappedBy );
207212

208213
// HHH-6813
209214
// Foo: @Id long id, @OneToOne(mappedBy="foo") Bar bar
@@ -212,16 +217,16 @@ private void bindTargetManyToOne(
212217
boolean referenceToPrimaryKey = mappedBy == null
213218
|| targetEntityIdentifier instanceof Component
214219
&& !( (Component) targetEntityIdentifier ).hasProperty( mappedBy );
215-
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
220+
oneToOne.setReferenceToPrimaryKey( referenceToPrimaryKey );
216221

217-
final String propertyRef = value.getReferencedPropertyName();
222+
final String propertyRef = oneToOne.getReferencedPropertyName();
218223
if ( propertyRef != null ) {
219224
buildingContext.getMetadataCollector()
220-
.addUniquePropertyReference( value.getReferencedEntityName(), propertyRef );
225+
.addUniquePropertyReference( oneToOne.getReferencedEntityName(), propertyRef );
221226
}
222227
}
223228

224-
private Property targetProperty(OneToOne value, PersistentClass targetEntity) {
229+
private Property targetProperty(OneToOne oneToOne, PersistentClass targetEntity) {
225230
try {
226231
Property targetProperty = findPropertyByName( targetEntity, mappedBy );
227232
if ( targetProperty != null ) {
@@ -233,10 +238,10 @@ private Property targetProperty(OneToOne value, PersistentClass targetEntity) {
233238
}
234239
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
235240
+ "' is 'mappedBy' a property named '" + mappedBy
236-
+ "' which does not exist in the target entity type '" + value.getReferencedEntityName() + "'" );
241+
+ "' which does not exist in the target entity type '" + oneToOne.getReferencedEntityName() + "'" );
237242
}
238243

239-
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne value, String propertyName, Property property) {
244+
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, String propertyName, Property property) {
240245
// we need to check if the columns are in the right order
241246
// if not, then we need to create a many to one and formula
242247
// but actually, since entities linked by a one to one need
@@ -245,16 +250,16 @@ private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOn
245250

246251
if ( rightOrder ) {
247252
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
248-
value,
253+
oneToOne,
249254
joinColumns,
250255
!optional, //cannot have nullable and unique on certain DBs
251256
propertyHolder.getPersistentClass(),
252257
qualify( propertyHolder.getPath(), propertyName),
253258
buildingContext
254259
);
255260
secondPass.doSecondPass(persistentClasses);
256-
//no column associated since its a one to one
257-
propertyHolder.addProperty(property, inferredData.getDeclaringClass() );
261+
//no column associated since it's a one to one
262+
propertyHolder.addProperty( property, inferredData.getDeclaringClass() );
258263
}
259264
// else {
260265
// this is a @ManyToOne with Formula

hibernate-core/src/main/java/org/hibernate/cfg/ToOneBinder.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ else if ( lazy != null ) {
327327
toOne.setLazy( fetchType == FetchType.LAZY );
328328
toOne.setUnwrapProxy( proxyless || fetchType == FetchType.EAGER );
329329
toOne.setUnwrapProxyImplicit( true );
330+
toOne.setLazyPolymorphismAllowed( !proxyless );
330331
}
331332

332333
final Fetch fetch = property.getAnnotation( Fetch.class );
@@ -447,7 +448,8 @@ private static void bindOneToOne(
447448
//column.getTable() => persistentClass.getTable()
448449
final String propertyName = inferredData.getPropertyName();
449450
LOG.tracev( "Fetching {0} with {1}", propertyName, fetchMode );
450-
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne ) || !isEmptyAnnotationValue( mappedBy ) ) {
451+
if ( isMapToPK( joinColumns, propertyHolder, trueOneToOne )
452+
|| !isEmptyAnnotationValue( mappedBy ) ) {
451453
//is a true one-to-one
452454
//FIXME referencedColumnName ignored => ordering may fail.
453455
final OneToOneSecondPass secondPass = new OneToOneSecondPass(
@@ -497,7 +499,7 @@ private static boolean isMapToPK(AnnotatedJoinColumns joinColumns, PropertyHolde
497499
}
498500
else {
499501
//try to find a hidden true one to one (FK == PK columns)
500-
KeyValue identifier = propertyHolder.getIdentifier();
502+
final KeyValue identifier = propertyHolder.getIdentifier();
501503
if ( identifier == null ) {
502504
//this is a @OneToOne in an @EmbeddedId (the persistentClass.identifier is not set yet, it's being built)
503505
//by definition the PK cannot refer to itself so it cannot map to itself
@@ -568,23 +570,24 @@ else if ( joinColumn != null ) {
568570
}
569571

570572
private static boolean noConstraint(ForeignKey joinColumns, MetadataBuildingContext context) {
571-
return joinColumns != null
572-
&& ( joinColumns.value() == ConstraintMode.NO_CONSTRAINT
573-
|| joinColumns.value() == ConstraintMode.PROVIDER_DEFAULT
574-
&& context.getBuildingOptions().isNoConstraintByDefault() );
575-
}
576-
577-
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
578-
if ( AnnotationBinder.isDefault( targetEntity, context ) ) {
579-
return propertyData.getClassOrElementName();
573+
if (joinColumns == null) {
574+
return false;
580575
}
581576
else {
582-
return targetEntity.getName();
577+
ConstraintMode mode = joinColumns.value();
578+
return mode == ConstraintMode.NO_CONSTRAINT || mode == ConstraintMode.PROVIDER_DEFAULT
579+
&& context.getBuildingOptions().isNoConstraintByDefault();
583580
}
584581
}
585582

583+
public static String getReferenceEntityName(PropertyData propertyData, XClass targetEntity, MetadataBuildingContext context) {
584+
return AnnotationBinder.isDefault( targetEntity, context )
585+
? propertyData.getClassOrElementName()
586+
: targetEntity.getName();
587+
}
588+
586589
public static String getReferenceEntityName(PropertyData propertyData, MetadataBuildingContext context) {
587-
XClass targetEntity = getTargetEntity( propertyData, context );
590+
final XClass targetEntity = getTargetEntity( propertyData, context );
588591
return AnnotationBinder.isDefault( targetEntity, context )
589592
? propertyData.getClassOrElementName()
590593
: targetEntity.getName();

hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,19 @@ else if ( property != null) {
9595

9696
public void doSecondPass(java.util.Map<String, PersistentClass> persistentClasses) throws MappingException {
9797
if ( value instanceof ManyToOne ) {
98+
//TODO: move this validation logic to a separate ManyToOnSecondPass
99+
// for consistency with how this is handled for OneToOnes
98100
final ManyToOne manyToOne = (ManyToOne) value;
99101
final PersistentClass targetEntity = persistentClasses.get( manyToOne.getReferencedEntityName() );
100102
if ( targetEntity == null ) {
101103
throw new AnnotationException( "Association '" + qualify( entityClassName, path )
102104
+ "' targets an unknown entity named '" + manyToOne.getReferencedEntityName() + "'" );
103105
}
106+
if ( !manyToOne.isLazyPolymorphismAllowed() && manyToOne.isLazy() && targetEntity.hasSubclasses() ) {
107+
throw new AnnotationException( "Association '" + qualify( entityClassName, path )
108+
+ "' is annotated '@Proxyless' but targets an entity named '"
109+
+ manyToOne.getReferencedEntityName() + "' which has subclasses" );
110+
}
104111
manyToOne.setPropertyName( path );
105112
createSyntheticPropertyReference(
106113
columns,

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
2727
private String propertyName;
2828
private boolean lazy = true;
2929
private boolean sorted;
30-
protected boolean unwrapProxy;
31-
protected boolean isUnwrapProxyImplicit;
32-
protected boolean referenceToPrimaryKey = true;
30+
private boolean unwrapProxy;
31+
private boolean unwrapProxyImplicit;
32+
private boolean referenceToPrimaryKey = true;
33+
34+
private boolean lazyPolymorphismAllowed = true;
3335

3436
protected ToOne(MetadataBuildingContext buildingContext, Table table) {
3537
super( buildingContext, table );
@@ -44,7 +46,7 @@ protected ToOne(ToOne original) {
4446
this.lazy = original.lazy;
4547
this.sorted = original.sorted;
4648
this.unwrapProxy = original.unwrapProxy;
47-
this.isUnwrapProxyImplicit = original.isUnwrapProxyImplicit;
49+
this.unwrapProxyImplicit = original.unwrapProxyImplicit;
4850
this.referenceToPrimaryKey = original.referenceToPrimaryKey;
4951
}
5052

@@ -69,7 +71,7 @@ public String getReferencedEntityName() {
6971
}
7072

7173
public void setReferencedEntityName(String referencedEntityName) {
72-
this.referencedEntityName = referencedEntityName==null ?
74+
this.referencedEntityName = referencedEntityName==null ?
7375
null : referencedEntityName.intern();
7476
}
7577

@@ -135,15 +137,23 @@ public void setUnwrapProxy(boolean unwrapProxy) {
135137
}
136138

137139
public boolean isUnwrapProxyImplicit() {
138-
return isUnwrapProxyImplicit;
140+
return unwrapProxyImplicit;
141+
}
142+
143+
public boolean isLazyPolymorphismAllowed() {
144+
return lazyPolymorphismAllowed;
145+
}
146+
147+
public void setLazyPolymorphismAllowed(boolean lazyPolymorphismAllowed) {
148+
this.lazyPolymorphismAllowed = lazyPolymorphismAllowed;
139149
}
140150

141151
/**
142152
* Related to HHH-13658 - keep track of whether `unwrapProxy` is an implicit value
143153
* for reference later
144154
*/
145155
public void setUnwrapProxyImplicit(boolean unwrapProxyImplicit) {
146-
isUnwrapProxyImplicit = unwrapProxyImplicit;
156+
this.unwrapProxyImplicit = unwrapProxyImplicit;
147157
}
148158

149159
public boolean isReferenceToPrimaryKey() {

0 commit comments

Comments
 (0)