From 0ab6c1178b0e09ad2f1065f4bc8bef46a49f6ae1 Mon Sep 17 00:00:00 2001 From: Fernando Guardiola Date: Mon, 9 Jul 2018 16:01:23 +0200 Subject: [PATCH] HHH-12770 - Document @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY behavior --- .../chapters/domain/associations.adoc | 8 +- .../userguide/associations/NotFoundTest.java | 2 +- .../org/hibernate/cfg/AnnotationBinder.java | 12 ++ .../hibernate/internal/CoreMessageLogger.java | 6 + .../JoinFormulaManyToOneLazyFetchingTest.java | 178 ++++++++++++++++++ ...ulaManyToOneNotIgnoreLazyFetchingTest.java | 172 +++++++++++++++++ ...ulaOneToManyNotIgnoreLazyFetchingTest.java | 174 +++++++++++++++++ ...mulaOneToOneNotIgnoreLazyFetchingTest.java | 169 +++++++++++++++++ 8 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneLazyFetchingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index a8d83ab014..da4d370a45 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -425,6 +425,12 @@ However, you can configure this behavior so that Hibernate can ignore such an Ex To ignore non-existing parent entity references, even though not really recommended, it's possible to use the annotation `org.hibernate.annotation.NotFound` annotation with a value of `org.hibernate.annotations.NotFoundAction.IGNORE`. +[NOTE] +==== +`@ManyToOne` and `@OneToOne` associations annotated with `@NotFound(action = NotFoundAction.IGNORE)` are always fetched eagerly +even if you set the `fetch` attribute to `FetchType.LAZY`. +==== + Considering the following `City` and `Person` entity mappings: [[associations-not-found-domain-model-example]] @@ -439,7 +445,7 @@ include::{sourcedir}/NotFoundTest.java[tags=associations-not-found-domain-model- If we have the following entities in our database: [[associations-not-found-persist-example]] -.`@NotFound` mapping example +.`@NotFound` persist example ==== [source,java] ---- diff --git a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java index 82b9442fce..004c51a5c6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/associations/NotFoundTest.java @@ -82,7 +82,7 @@ public class NotFoundTest extends BaseEntityManagerFunctionalTestCase { private String cityName; - @ManyToOne( fetch = FetchType.LAZY ) + @ManyToOne @NotFound ( action = NotFoundAction.IGNORE ) @JoinColumn( name = "cityName", 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 5f42153717..7e60e257ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -1750,6 +1750,7 @@ public final class AnnotationBinder { Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); + matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); JoinTable assocTable = propertyHolder.getJoinTable( property ); @@ -1794,6 +1795,7 @@ public final class AnnotationBinder { Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); + matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); JoinTable assocTable = propertyHolder.getJoinTable( property ); @@ -3525,4 +3527,14 @@ public final class AnnotationBinder { } return false; } + + private static void matchIgnoreNotFoundWithFetchType( + String entity, + String association, + boolean ignoreNotFound, + FetchType fetchType) { + if ( ignoreNotFound && fetchType == FetchType.LAZY ) { + LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 8bfdbee776..da3146d531 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1818,4 +1818,10 @@ public interface CoreMessageLogger extends BasicLogger { @LogMessage(level = INFO) @Message(value = "Using JtaPlatform implementation: [%s]", id = 490) void usingJtaPlatform(String jtaPlatformClassName); + + @LogMessage(level = WARN) + @Message(value = "The [%2$s] association in the [%1$s] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. " + + "The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", id = 491) + void ignoreNotFoundWithFetchTypeLazy(String entity, String association); + } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneLazyFetchingTest.java new file mode 100644 index 0000000000..0bc7aa878c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneLazyFetchingTest.java @@ -0,0 +1,178 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.formula; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityNotFoundException; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.LazyInitializationException; +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12770") +public class JoinFormulaManyToOneLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Stock.class, + StockCode.class, + }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + StockCode code = new StockCode(); + code.setId( 1L ); + code.setCopeType( CodeType.TYPE_A ); + code.setRefNumber( "ABC" ); + entityManager.persist( code ); + + Stock stock1 = new Stock(); + stock1.setId( 1L ); + stock1.setCode( code ); + entityManager.persist( stock1 ); + + Stock stock2 = new Stock(); + stock2.setId( 2L ); + entityManager.persist( stock2 ); + } ); + } + + @Test + public void testLazyLoading() { + List stocks = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "SELECT s FROM Stock s", Stock.class ) + .getResultList(); + } ); + assertEquals( 2, stocks.size() ); + + try { + assertEquals( "ABC", stocks.get( 0 ).getCode().getRefNumber() ); + + fail( "Should have thrown LazyInitializationException" ); + } + catch (LazyInitializationException expected) { + + } + } + + @Test + public void testEagerLoading() { + doInJPA( this::entityManagerFactory, entityManager -> { + List stocks = entityManager.createQuery( + "SELECT s FROM Stock s", Stock.class ) + .getResultList(); + + assertEquals( 2, stocks.size() ); + assertEquals( "ABC", stocks.get( 0 ).getCode().getRefNumber() ); + + try { + stocks.get( 1 ).getCode().getRefNumber(); + + fail( "Should have thrown EntityNotFoundException" ); + } + catch (EntityNotFoundException expected) { + + } + + } ); + } + + @Entity(name = "Stock") + public static class Stock implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumnOrFormula(column = @JoinColumn(name = "CODE_ID", referencedColumnName = "ID")) + @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "TYPE", value = "'TYPE_A'")) + private StockCode code; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public StockCode getCode() { + return code; + } + + public void setCode(StockCode code) { + this.code = code; + } + } + + @Entity(name = "StockCode") + public static class StockCode implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @Id + @Enumerated(EnumType.STRING) + @Column(name = "TYPE") + private CodeType copeType; + + private String refNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CodeType getCopeType() { + return copeType; + } + + public void setCopeType(CodeType copeType) { + this.copeType = copeType; + } + + public String getRefNumber() { + return refNumber; + } + + public void setRefNumber(String refNumber) { + this.refNumber = refNumber; + } + } + + public enum CodeType { + TYPE_A, TYPE_B; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java new file mode 100644 index 0000000000..080bec52fa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java @@ -0,0 +1,172 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.formula; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.LazyInitializationException; +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.boot.model.process.internal.ScanningCoordinator; +import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12770") +public class JoinFormulaManyToOneNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) + ); + + private Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" ); + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Stock.class, + StockCode.class, + }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + StockCode code = new StockCode(); + code.setId( 1L ); + code.setCopeType( CodeType.TYPE_A ); + code.setRefNumber( "ABC" ); + entityManager.persist( code ); + + Stock stock1 = new Stock(); + stock1.setId( 1L ); + stock1.setCode( code ); + entityManager.persist( stock1 ); + + Stock stock2 = new Stock(); + stock2.setId( 2L ); + entityManager.persist( stock2 ); + } ); + } + + @Test + public void testLazyLoading() { + + assertEquals( "HHH000491: The [code] association in the [org.hibernate.test.annotations.formula.JoinFormulaManyToOneNotIgnoreLazyFetchingTest$Stock] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", triggerable.triggerMessage() ); + + List stocks = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "SELECT s FROM Stock s", Stock.class ) + .getResultList(); + } ); + assertEquals( 2, stocks.size() ); + + assertEquals( "ABC", stocks.get( 0 ).getCode().getRefNumber() ); + assertNull( stocks.get( 1 ).getCode() ); + } + + @Entity(name = "Stock") + public static class Stock implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumnOrFormula(column = @JoinColumn(name = "CODE_ID", referencedColumnName = "ID")) + @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "TYPE", value = "'TYPE_A'")) + private StockCode code; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public StockCode getCode() { + return code; + } + + public void setCode(StockCode code) { + this.code = code; + } + } + + @Entity(name = "StockCode") + public static class StockCode implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @Id + @Enumerated(EnumType.STRING) + @Column(name = "TYPE") + private CodeType copeType; + + private String refNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CodeType getCopeType() { + return copeType; + } + + public void setCopeType(CodeType copeType) { + this.copeType = copeType; + } + + public String getRefNumber() { + return refNumber; + } + + public void setRefNumber(String refNumber) { + this.refNumber = refNumber; + } + } + + public enum CodeType { + TYPE_A, TYPE_B; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java new file mode 100644 index 0000000000..eb16c23d6f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java @@ -0,0 +1,174 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.formula; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.LazyInitializationException; +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12770") +public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) + ); + + private Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" ); + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Stock.class, + StockCode.class, + }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + StockCode code = new StockCode(); + code.setId( 1L ); + code.setCopeType( CodeType.TYPE_A ); + code.setRefNumber( "ABC" ); + entityManager.persist( code ); + + Stock stock1 = new Stock(); + stock1.setId( 1L ); + stock1.getCodes().add( code ); + entityManager.persist( stock1 ); + + Stock stock2 = new Stock(); + stock2.setId( 2L ); + entityManager.persist( stock2 ); + } ); + } + + @Test + public void testLazyLoading() { + + assertFalse( triggerable.wasTriggered() ); + + List stocks = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "SELECT s FROM Stock s", Stock.class ) + .getResultList(); + } ); + assertEquals( 2, stocks.size() ); + + try { + assertEquals( "ABC", stocks.get( 0 ).getCodes().get( 0 ).getRefNumber() ); + + fail( "Should have thrown LazyInitializationException" ); + } + catch (LazyInitializationException expected) { + + } + } + + @Entity(name = "Stock") + public static class Stock implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @OneToMany + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "CODE_ID", referencedColumnName = "ID") + private List codes = new ArrayList<>( ); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getCodes() { + return codes; + } + } + + @Entity(name = "StockCode") + public static class StockCode implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @Id + @Enumerated(EnumType.STRING) + @Column(name = "TYPE") + private CodeType copeType; + + private String refNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CodeType getCopeType() { + return copeType; + } + + public void setCopeType(CodeType copeType) { + this.copeType = copeType; + } + + public String getRefNumber() { + return refNumber; + } + + public void setRefNumber(String refNumber) { + this.refNumber = refNumber; + } + } + + public enum CodeType { + TYPE_A, TYPE_B; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java new file mode 100644 index 0000000000..86e95f97d8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java @@ -0,0 +1,169 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.formula; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@TestForIssue(jiraKey = "HHH-12770") +public class JoinFormulaOneToOneNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AnnotationBinder.class.getName() ) + ); + + private Triggerable triggerable = logInspection.watchForLogMessages( "HHH000491" ); + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Stock.class, + StockCode.class, + }; + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + StockCode code = new StockCode(); + code.setId( 1L ); + code.setCopeType( CodeType.TYPE_A ); + code.setRefNumber( "ABC" ); + entityManager.persist( code ); + + Stock stock1 = new Stock(); + stock1.setId( 1L ); + stock1.setCode( code ); + entityManager.persist( stock1 ); + + Stock stock2 = new Stock(); + stock2.setId( 2L ); + entityManager.persist( stock2 ); + } ); + } + + @Test + public void testLazyLoading() { + + assertEquals( "HHH000491: The [code] association in the [org.hibernate.test.annotations.formula.JoinFormulaOneToOneNotIgnoreLazyFetchingTest$Stock] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", triggerable.triggerMessage() ); + + List stocks = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.createQuery( + "SELECT s FROM Stock s", Stock.class ) + .getResultList(); + } ); + assertEquals( 2, stocks.size() ); + + assertEquals( "ABC", stocks.get( 0 ).getCode().getRefNumber() ); + assertNull( stocks.get( 1 ).getCode() ); + } + + @Entity(name = "Stock") + public static class Stock implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumnOrFormula(column = @JoinColumn(name = "CODE_ID", referencedColumnName = "ID")) + @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "TYPE", value = "'TYPE_A'")) + private StockCode code; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public StockCode getCode() { + return code; + } + + public void setCode(StockCode code) { + this.code = code; + } + } + + @Entity(name = "StockCode") + public static class StockCode implements Serializable { + + @Id + @Column(name = "ID") + private Long id; + + @Id + @Enumerated(EnumType.STRING) + @Column(name = "TYPE") + private CodeType copeType; + + private String refNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CodeType getCopeType() { + return copeType; + } + + public void setCopeType(CodeType copeType) { + this.copeType = copeType; + } + + public String getRefNumber() { + return refNumber; + } + + public void setRefNumber(String refNumber) { + this.refNumber = refNumber; + } + } + + public enum CodeType { + TYPE_A, TYPE_B; + } + +}