HHH-15509 correctly support @NotFound @ManyToMany

1. error if a non-@ManyToMany collection is @NotFound
2. disable constraint generation for @NotFound @ManyToMany
3. allow lazy fetching for @NotFound @ManyToMany
4. rework a completely bogus test so it makes sense
This commit is contained in:
Gavin King 2022-09-26 13:11:27 +02:00
parent 5b907ae8b1
commit 7d34f86a95
4 changed files with 91 additions and 87 deletions

View File

@ -1771,8 +1771,8 @@ public final class AnnotationBinder {
final JavaType<?> domainJtd = jtdRegistry.resolveDescriptor( domainJavaClass ); final JavaType<?> domainJtd = jtdRegistry.resolveDescriptor( domainJavaClass );
final JavaType<?> relationalJtd = jtdRegistry.resolveDescriptor( relationalJavaClass ); final JavaType<?> relationalJtd = jtdRegistry.resolveDescriptor( relationalJavaClass );
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"rawtypes", "unchecked"})
final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl( final JpaAttributeConverterImpl<?,?> valueConverter = new JpaAttributeConverterImpl(
bean, bean,
converterJtd, converterJtd,
domainJtd, domainJtd,
@ -2474,10 +2474,7 @@ public final class AnnotationBinder {
&& isToManyAssociationWithinEmbeddableCollection(propertyHolder) ) { && isToManyAssociationWithinEmbeddableCollection(propertyHolder) ) {
throw new AnnotationException( throw new AnnotationException(
"@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection: " "@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection: "
+ BinderHelper.getPath( + BinderHelper.getPath(propertyHolder, inferredData)
propertyHolder,
inferredData
)
); );
} }
@ -2485,10 +2482,7 @@ public final class AnnotationBinder {
&& manyToManyAnn != null && !manyToManyAnn.mappedBy().isEmpty() ) { && manyToManyAnn != null && !manyToManyAnn.mappedBy().isEmpty() ) {
throw new AnnotationException( throw new AnnotationException(
"Explicit @OrderColumn on inverse side of @ManyToMany is illegal: " "Explicit @OrderColumn on inverse side of @ManyToMany is illegal: "
+ BinderHelper.getPath( + BinderHelper.getPath(propertyHolder, inferredData)
propertyHolder,
inferredData
)
); );
} }
@ -2519,7 +2513,13 @@ public final class AnnotationBinder {
collectionBinder.setPropertyHolder(propertyHolder); collectionBinder.setPropertyHolder(propertyHolder);
Cascade hibernateCascade = property.getAnnotation( Cascade.class ); Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class ); NotFound notFound = property.getAnnotation( NotFound.class );
collectionBinder.setNotFoundAction( notFound == null ? null : notFound.action() ); if ( notFound != null ) {
if ( manyToManyAnn == null ) {
throw new AnnotationException("collection annotated @NotFound is not a @ManyToMany association: "
+ BinderHelper.getPath(propertyHolder, inferredData) );
}
collectionBinder.setNotFoundAction( notFound.action() );
}
collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() ); collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() );
collectionBinder.setAccessType( inferredData.getDefaultAccess() ); collectionBinder.setAccessType( inferredData.getDefaultAccess() );
@ -3022,7 +3022,7 @@ public final class AnnotationBinder {
return null; return null;
} }
protected static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType( static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
XProperty property, XProperty property,
XClass returnedClass, XClass returnedClass,
MetadataBuildingContext context) { MetadataBuildingContext context) {

View File

@ -60,7 +60,6 @@ import org.hibernate.annotations.ListIndexJdbcType;
import org.hibernate.annotations.ListIndexJdbcTypeCode; import org.hibernate.annotations.ListIndexJdbcTypeCode;
import org.hibernate.annotations.Loader; import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OnDeleteAction;
@ -971,7 +970,6 @@ public abstract class CollectionBinder {
ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class );
ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class );
NotFound notFound = property.getAnnotation( NotFound.class );
FetchType fetchType; FetchType fetchType;
if ( oneToMany != null ) { if ( oneToMany != null ) {
@ -991,55 +989,35 @@ public abstract class CollectionBinder {
"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements" "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
); );
} }
if ( notFound != null ) {
collection.setLazy( false );
if ( lazy != null ) { if ( lazy != null ) {
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
else {
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
collection.setLazy( false );
} }
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
if ( fetch != null ) { collection.setFetchMode( FetchMode.SELECT );
if ( fetch.value() != null ) { }
collection.setFetchMode( fetch.value().getHibernateFetchMode() ); else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { collection.setFetchMode( FetchMode.SELECT );
collection.setSubselectLoadable( true ); collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true ); collection.getOwner().setSubselectLoadableCollections( true );
}
}
} }
else { else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
} }
} }
else { else {
if ( lazy != null ) { collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
else {
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
collection.setLazy( false );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
collection.setFetchMode( FetchMode.SELECT );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
collection.setFetchMode( FetchMode.SELECT );
collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true );
}
else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
}
}
else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
} }
} }
@ -2254,7 +2232,7 @@ public abstract class CollectionBinder {
* collection element * collection element
* Otherwise delegates to the usual algorithm * Otherwise delegates to the usual algorithm
*/ */
public static void bindManytoManyInverseFk( public void bindManytoManyInverseFk(
PersistentClass referencedEntity, PersistentClass referencedEntity,
AnnotatedJoinColumn[] columns, AnnotatedJoinColumn[] columns,
SimpleValue value, SimpleValue value,
@ -2302,6 +2280,9 @@ public abstract class CollectionBinder {
} }
else { else {
BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, buildingContext ); BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, buildingContext );
if ( notFoundAction == NotFoundAction.IGNORE ) {
value.disableForeignKey();
}
TableBinder.bindFk( referencedEntity, null, columns, value, unique, buildingContext ); TableBinder.bindFk( referencedEntity, null, columns, value, unique, buildingContext );
} }
} }
@ -2334,12 +2315,6 @@ public abstract class CollectionBinder {
this.notFoundAction = notFoundAction; this.notFoundAction = notFoundAction;
} }
public void setIgnoreNotFound(boolean ignoreNotFound) {
this.notFoundAction = ignoreNotFound
? NotFoundAction.IGNORE
: null;
}
public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) { public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) {
this.mapKeyColumns = mapKeyColumns; this.mapKeyColumns = mapKeyColumns;
} }

View File

@ -6,46 +6,40 @@
*/ */
package org.hibernate.orm.test.annotations.formula; package org.hibernate.orm.test.annotations.formula;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.JoinTable;
import jakarta.persistence.OneToMany; import jakarta.persistence.ManyToMany;
import org.hibernate.Hibernate;
import org.hibernate.LazyInitializationException;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.LoggerInspectionRule;
import org.hibernate.testing.logger.Triggerable; import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.transaction.TransactionUtil2; import org.jboss.logging.Logger;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.jboss.logging.Logger; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.hibernate.testing.transaction.TransactionUtil2.fromTransaction; import static org.hibernate.testing.transaction.TransactionUtil2.fromTransaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.fail;
@TestForIssue(jiraKey = "HHH-12770") @TestForIssue(jiraKey = {"HHH-12770", "HHH-15545"})
public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { public class ManyToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase {
@Rule @Rule
public LoggerInspectionRule logInspection = new LoggerInspectionRule( public LoggerInspectionRule logInspection = new LoggerInspectionRule(
@ -80,6 +74,9 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
Stock stock2 = new Stock(); Stock stock2 = new Stock();
stock2.setId( 2L ); stock2.setId( 2L );
entityManager.persist( stock2 ); entityManager.persist( stock2 );
entityManager.flush();
entityManager.remove(code);
} ); } );
} }
@ -88,8 +85,13 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
assertFalse( triggerable.wasTriggered() ); assertFalse( triggerable.wasTriggered() );
List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> { List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), session -> {
return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList(); List<Stock> list = session.createQuery("select s from Stock s order by id", Stock.class).getResultList();
for (Stock s: list) {
assertFalse( Hibernate.isInitialized( s.getCodes() ) );
Hibernate.initialize( s.getCodes() );
}
return list;
} ); } );
assertThat( stocks ).hasSize( 2 ); assertThat( stocks ).hasSize( 2 );
@ -97,7 +99,30 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
final Stock firstStock = stocks.get( 0 ); final Stock firstStock = stocks.get( 0 );
final Stock secondStock = stocks.get( 1 ); final Stock secondStock = stocks.get( 1 );
assertThat( firstStock.getCodes() ).hasSize( 1 ); assertThat( firstStock.getCodes() ).hasSize( 0 );
assertThat( secondStock.getCodes() ).hasSize( 0 );
}
@Test
public void testEagerLoading() {
assertFalse( triggerable.wasTriggered() );
List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ),
session -> session.createQuery("select s from Stock s left join fetch s.codes order by s.id", Stock.class)
.getResultList()
);
assertThat( stocks ).hasSize( 2 );
for (Stock s: stocks) {
assertTrue( Hibernate.isInitialized( s.getCodes() ) );
}
final Stock firstStock = stocks.get( 0 );
final Stock secondStock = stocks.get( 1 );
assertThat( firstStock.getCodes() ).hasSize( 0 );
assertThat( secondStock.getCodes() ).hasSize( 0 ); assertThat( secondStock.getCodes() ).hasSize( 0 );
} }
@ -108,10 +133,15 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
@Column(name = "ID") @Column(name = "ID")
private Long id; private Long id;
@OneToMany @ManyToMany
@NotFound(action = NotFoundAction.IGNORE) @NotFound(action = NotFoundAction.IGNORE)
@JoinColumn(name = "CODE_ID", referencedColumnName = "ID") @JoinTable(name = "STOCK_BY_CODE",
private List<StockCode> codes = new ArrayList<>( ); joinColumns = @JoinColumn(name = "STOCK_ID", referencedColumnName = "ID"),
inverseJoinColumns = {
@JoinColumn(name = "CODE_ID", referencedColumnName = "ID"),
@JoinColumn(name = "CODE_TYPE", referencedColumnName = "TYPE")
})
private List<StockCode> codes = new ArrayList<>();
public Long getId() { public Long getId() {
return id; return id;

View File

@ -36,8 +36,7 @@ public class OneToManyNotAuditedNullEntity implements Serializable {
private String data; private String data;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@OneToMany(fetch = FetchType.LAZY) @OneToMany(fetch = FetchType.EAGER)
@NotFound(action = NotFoundAction.IGNORE)
@JoinTable(joinColumns = @JoinColumn(name = "O2MNotAudited_id")) @JoinTable(joinColumns = @JoinColumn(name = "O2MNotAudited_id"))
private List<UnversionedStrTestEntity> references = new ArrayList<UnversionedStrTestEntity>(); private List<UnversionedStrTestEntity> references = new ArrayList<UnversionedStrTestEntity>();