diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index f6b4a4e9f4..8fa3d44e66 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -1771,8 +1771,8 @@ public final class AnnotationBinder { final JavaType domainJtd = jtdRegistry.resolveDescriptor( domainJavaClass ); final JavaType relationalJtd = jtdRegistry.resolveDescriptor( relationalJavaClass ); - @SuppressWarnings({ "unchecked", "rawtypes" }) - final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl( + @SuppressWarnings({"rawtypes", "unchecked"}) + final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl( bean, converterJtd, domainJtd, @@ -2474,10 +2474,7 @@ public final class AnnotationBinder { && isToManyAssociationWithinEmbeddableCollection(propertyHolder) ) { throw new AnnotationException( "@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection: " - + BinderHelper.getPath( - propertyHolder, - inferredData - ) + + BinderHelper.getPath(propertyHolder, inferredData) ); } @@ -2485,10 +2482,7 @@ public final class AnnotationBinder { && manyToManyAnn != null && !manyToManyAnn.mappedBy().isEmpty() ) { throw new AnnotationException( "Explicit @OrderColumn on inverse side of @ManyToMany is illegal: " - + BinderHelper.getPath( - propertyHolder, - inferredData - ) + + BinderHelper.getPath(propertyHolder, inferredData) ); } @@ -2519,7 +2513,13 @@ public final class AnnotationBinder { collectionBinder.setPropertyHolder(propertyHolder); Cascade hibernateCascade = property.getAnnotation( Cascade.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.setAccessType( inferredData.getDefaultAccess() ); @@ -3022,7 +3022,7 @@ public final class AnnotationBinder { return null; } - protected static Class> resolveTimeZoneStorageCompositeUserType( + static Class> resolveTimeZoneStorageCompositeUserType( XProperty property, XClass returnedClass, MetadataBuildingContext context) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 76d5718ec1..bd738675f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -60,7 +60,6 @@ import org.hibernate.annotations.ListIndexJdbcType; import org.hibernate.annotations.ListIndexJdbcTypeCode; import org.hibernate.annotations.Loader; import org.hibernate.annotations.ManyToAny; -import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; @@ -971,7 +970,6 @@ public abstract class CollectionBinder { ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); - NotFound notFound = property.getAnnotation( NotFound.class ); FetchType fetchType; 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" ); } - if ( notFound != null ) { - collection.setLazy( false ); - if ( lazy != null ) { - collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); + if ( lazy != null ) { + 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 ); } - - if ( fetch != null ) { - if ( fetch.value() != null ) { - collection.setFetchMode( fetch.value().getHibernateFetchMode() ); - if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { - collection.setSubselectLoadable( true ); - collection.getOwner().setSubselectLoadableCollections( true ); - } - } + 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 { - collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); + throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); } } else { - if ( lazy != null ) { - 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 ) ); - } + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); } } @@ -2254,7 +2232,7 @@ public abstract class CollectionBinder { * collection element * Otherwise delegates to the usual algorithm */ - public static void bindManytoManyInverseFk( + public void bindManytoManyInverseFk( PersistentClass referencedEntity, AnnotatedJoinColumn[] columns, SimpleValue value, @@ -2302,6 +2280,9 @@ public abstract class CollectionBinder { } else { BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, buildingContext ); + if ( notFoundAction == NotFoundAction.IGNORE ) { + value.disableForeignKey(); + } TableBinder.bindFk( referencedEntity, null, columns, value, unique, buildingContext ); } } @@ -2334,12 +2315,6 @@ public abstract class CollectionBinder { this.notFoundAction = notFoundAction; } - public void setIgnoreNotFound(boolean ignoreNotFound) { - this.notFoundAction = ignoreNotFound - ? NotFoundAction.IGNORE - : null; - } - public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) { this.mapKeyColumns = mapKeyColumns; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/ManyToManyNotIgnoreLazyFetchingTest.java similarity index 67% rename from hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/ManyToManyNotIgnoreLazyFetchingTest.java index 1403281f29..34929a05ff 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/ManyToManyNotIgnoreLazyFetchingTest.java @@ -6,46 +6,40 @@ */ 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.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; - -import org.hibernate.LazyInitializationException; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import org.hibernate.Hibernate; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.cfg.AnnotationBinder; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.Triggerable; -import org.hibernate.testing.transaction.TransactionUtil2; +import org.jboss.logging.Logger; import org.junit.Rule; 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.hibernate.testing.transaction.TransactionUtil.doInJPA; 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.assertNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; -@TestForIssue(jiraKey = "HHH-12770") -public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { +@TestForIssue(jiraKey = {"HHH-12770", "HHH-15545"}) +public class ManyToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { @Rule public LoggerInspectionRule logInspection = new LoggerInspectionRule( @@ -80,6 +74,9 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan Stock stock2 = new Stock(); stock2.setId( 2L ); entityManager.persist( stock2 ); + entityManager.flush(); + + entityManager.remove(code); } ); } @@ -88,8 +85,13 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan assertFalse( triggerable.wasTriggered() ); - List stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> { - return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList(); + List stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), session -> { + List 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 ); @@ -97,7 +99,30 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan final Stock firstStock = stocks.get( 0 ); 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 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 ); } @@ -108,10 +133,15 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan @Column(name = "ID") private Long id; - @OneToMany + @ManyToMany @NotFound(action = NotFoundAction.IGNORE) - @JoinColumn(name = "CODE_ID", referencedColumnName = "ID") - private List codes = new ArrayList<>( ); + @JoinTable(name = "STOCK_BY_CODE", + joinColumns = @JoinColumn(name = "STOCK_ID", referencedColumnName = "ID"), + inverseJoinColumns = { + @JoinColumn(name = "CODE_ID", referencedColumnName = "ID"), + @JoinColumn(name = "CODE_TYPE", referencedColumnName = "TYPE") + }) + private List codes = new ArrayList<>(); public Long getId() { return id; diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/onetomany/OneToManyNotAuditedNullEntity.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/onetomany/OneToManyNotAuditedNullEntity.java index cd1b356d7a..0af96aaba3 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/onetomany/OneToManyNotAuditedNullEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/onetomany/OneToManyNotAuditedNullEntity.java @@ -36,8 +36,7 @@ public class OneToManyNotAuditedNullEntity implements Serializable { private String data; @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) - @OneToMany(fetch = FetchType.LAZY) - @NotFound(action = NotFoundAction.IGNORE) + @OneToMany(fetch = FetchType.EAGER) @JoinTable(joinColumns = @JoinColumn(name = "O2MNotAudited_id")) private List references = new ArrayList();