HHH-12770 - Document @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY behavior

This commit is contained in:
Fernando Guardiola 2018-07-09 16:01:23 +02:00 committed by Vlad Mihalcea
parent 6c5e172609
commit 0ab6c1178b
8 changed files with 719 additions and 2 deletions

View File

@ -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]
----

View File

@ -82,7 +82,7 @@ public class NotFoundTest extends BaseEntityManagerFunctionalTestCase {
private String cityName;
@ManyToOne( fetch = FetchType.LAZY )
@ManyToOne
@NotFound ( action = NotFoundAction.IGNORE )
@JoinColumn(
name = "cityName",

View File

@ -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 );
}
}
}

View File

@ -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);
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Stock> 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<Stock> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Stock> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Stock> 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<StockCode> codes = new ArrayList<>( );
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<StockCode> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Stock> 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;
}
}