HHH-16191 change @NotFound semantic, do not force a join but trigger a subsequent select
This commit is contained in:
parent
88a5fd4d28
commit
10bfcabee2
|
@ -297,7 +297,7 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isOptional = ( (ManyToOne) bootValue ).isIgnoreNotFound();
|
isOptional = ( (ManyToOne) bootValue ).isIgnoreNotFound();
|
||||||
isInternalLoadNullable = ( isNullable && bootValue.isForeignKeyEnabled() ) || notFoundAction == NotFoundAction.IGNORE;
|
isInternalLoadNullable = ( isNullable && bootValue.isForeignKeyEnabled() ) || hasNotFoundAction();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert bootValue instanceof OneToOne;
|
assert bootValue instanceof OneToOne;
|
||||||
|
@ -1300,14 +1300,13 @@ public class ToOneAttributeMapping
|
||||||
&& parentNavigablePath.equals( fetchParent.getNavigablePath().getRealParent() );
|
&& parentNavigablePath.equals( fetchParent.getNavigablePath().getRealParent() );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
In case of @NotFound we are going to add fetch for the `fetchablePath` only if there is not already a `TableGroupJoin`.
|
In case of selected we are going to add a fetch for the `fetchablePath` only if there is not already a `TableGroupJoin`.
|
||||||
|
|
||||||
e.g. given :
|
e.g. given :
|
||||||
public static class EntityA {
|
public static class EntityA {
|
||||||
...
|
...
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
@NotFound(action = NotFoundAction.IGNORE)
|
|
||||||
private EntityB entityB;
|
private EntityB entityB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1324,7 +1323,7 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
having the left join we don't want to add an extra implicit join that will be translated into an SQL inner join (see HHH-15342)
|
having the left join we don't want to add an extra implicit join that will be translated into an SQL inner join (see HHH-15342)
|
||||||
*/
|
*/
|
||||||
if ( fetchTiming == FetchTiming.IMMEDIATE && selected || hasNotFoundAction() ) {
|
if ( fetchTiming == FetchTiming.IMMEDIATE && selected ) {
|
||||||
final TableGroup tableGroup = determineTableGroupForFetch(
|
final TableGroup tableGroup = determineTableGroupForFetch(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
fetchParent,
|
fetchParent,
|
||||||
|
@ -1336,9 +1335,11 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
return withRegisteredAssociationKeys(
|
return withRegisteredAssociationKeys(
|
||||||
() -> {
|
() -> {
|
||||||
final DomainResult<?> keyResult;
|
DomainResult<?> keyResult = null;
|
||||||
if ( notFoundAction != null ) {
|
|
||||||
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||||
|
// If the key side is non-nullable we also need to add the keyResult
|
||||||
|
// to be able to manually check invalid foreign key references
|
||||||
|
if ( notFoundAction != null || !isInternalLoadNullable ) {
|
||||||
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
parentTableGroup,
|
parentTableGroup,
|
||||||
|
@ -1346,7 +1347,9 @@ public class ToOneAttributeMapping
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
else if ( notFoundAction != null ) {
|
||||||
|
// For the target side only add keyResult when a not-found action is present
|
||||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
parentTableGroup,
|
parentTableGroup,
|
||||||
|
@ -1354,17 +1357,14 @@ public class ToOneAttributeMapping
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
keyResult = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EntityFetchJoinedImpl(
|
return new EntityFetchJoinedImpl(
|
||||||
fetchParent,
|
fetchParent,
|
||||||
this,
|
this,
|
||||||
tableGroup,
|
tableGroup,
|
||||||
keyResult,
|
keyResult,
|
||||||
fetchablePath,creationState
|
fetchablePath,
|
||||||
|
creationState
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
creationState
|
creationState
|
||||||
|
@ -1414,7 +1414,8 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
||||||
|
|
||||||
if ( fetchTiming == FetchTiming.IMMEDIATE ) {
|
// Consider all associations annotated with @NotFound as EAGER
|
||||||
|
if ( fetchTiming == FetchTiming.IMMEDIATE || hasNotFoundAction() ) {
|
||||||
return new EntityFetchSelectImpl(
|
return new EntityFetchSelectImpl(
|
||||||
fetchParent,
|
fetchParent,
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -77,11 +77,11 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
|
||||||
// need to also look at the foreign-key value column to check
|
// need to also look at the foreign-key value column to check
|
||||||
// for a dangling foreign-key
|
// for a dangling foreign-key
|
||||||
|
|
||||||
if ( notFoundAction != null && keyAssembler != null ) {
|
if ( keyAssembler != null ) {
|
||||||
final Object fkKeyValue = keyAssembler.assemble( rowProcessingState );
|
final Object fkKeyValue = keyAssembler.assemble( rowProcessingState );
|
||||||
if ( fkKeyValue != null ) {
|
if ( fkKeyValue != null ) {
|
||||||
if ( isMissing() ) {
|
if ( isMissing() ) {
|
||||||
if ( notFoundAction == NotFoundAction.EXCEPTION ) {
|
if ( notFoundAction != NotFoundAction.IGNORE ) {
|
||||||
throw new FetchNotFoundException(
|
throw new FetchNotFoundException(
|
||||||
referencedFetchable.getEntityMappingType().getEntityName(),
|
referencedFetchable.getEntityMappingType().getEntityName(),
|
||||||
fkKeyValue
|
fkKeyValue
|
||||||
|
|
|
@ -115,9 +115,8 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
||||||
|
|
||||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||||
|
|
||||||
// there should be 1 SQL statement with a join executed
|
// there should be 5 SQL statements executed
|
||||||
assertThat( paramterCounts ).hasSize( 1 );
|
assertThat( paramterCounts ).hasSize( 5 );
|
||||||
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
|
||||||
|
|
||||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||||
for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) {
|
for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) {
|
||||||
|
@ -158,9 +157,8 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
||||||
|
|
||||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||||
|
|
||||||
// there should be 1 SQL statement with a join executed
|
// there should be 8 SQL statements executed
|
||||||
assertThat( paramterCounts ).hasSize( 1 );
|
assertThat( paramterCounts ).hasSize( 8 );
|
||||||
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
|
||||||
|
|
||||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,8 @@ public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
// `@NotFound` forces EAGER join fetching
|
// `@NotFound` forces EAGER join fetching
|
||||||
assertThat( sqlInterceptor.getQueryCount() ).
|
assertThat( sqlInterceptor.getQueryCount() ).
|
||||||
describedAs( "Expecting 1 query (w/ join) due to `@NotFound`" )
|
describedAs( "Expecting 2 queries due to `@NotFound`" )
|
||||||
.isEqualTo( 1 );
|
.isEqualTo( 2 );
|
||||||
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
|
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
|
||||||
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
|
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
|
||||||
.isTrue();
|
.isTrue();
|
||||||
|
|
|
@ -76,8 +76,7 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// not-found associations are always join-fetched, so we should
|
// we should get `NUMBER_OF_ENTITIES` queries
|
||||||
// get `NUMBER_OF_ENTITIES` queries
|
|
||||||
assertEquals( NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
assertEquals( NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +93,8 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// not-found associations are always join-fetched, so we should
|
// we should get `NUMBER_OF_ENTITIES` queries
|
||||||
// get `NUMBER_OF_ENTITIES` queries
|
assertEquals( NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||||
assertThat( statistics.getPrepareStatementCount() ).isEqualTo( NUMBER_OF_ENTITIES );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -187,7 +185,7 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||||
@JoinColumn(name = "employer_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
|
@JoinColumn(name = "employer_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
|
||||||
@NotFound(action=NotFoundAction.IGNORE)
|
@NotFound(action=NotFoundAction.IGNORE)
|
||||||
private Employer employer;
|
private Employer employer;
|
||||||
|
|
|
@ -67,9 +67,10 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// not-found associations are always join-fetched, so we should
|
// The Employee#employer must be initialized immediately because
|
||||||
// get 1 query for the Employee with join
|
// enhanced proxies (and HibernateProxy objects) should never be created
|
||||||
assertEquals( 1, statistics.getPrepareStatementCount() );
|
// for a "not found" association.
|
||||||
|
assertEquals( 2, statistics.getPrepareStatementCount() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -85,9 +86,10 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// not-found associations are always join-fetched, so we should
|
// The Employee#employer must be initialized immediately because
|
||||||
// get 1 query for the Employee with join
|
// enhanced proxies (and HibernateProxy objects) should never be created
|
||||||
assertEquals( 1, statistics.getPrepareStatementCount() );
|
// for a "not found" association.
|
||||||
|
assertEquals( 2, statistics.getPrepareStatementCount() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.orm.test.bytecode.enhancement.lazy.proxy;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.BatchSize;
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
import org.hibernate.boot.SessionFactoryBuilder;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.ConstraintMode;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.ForeignKey;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andrea Boriero
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-11147")
|
||||||
|
@RunWith(BytecodeEnhancerRunner.class)
|
||||||
|
@EnhancementOptions(lazyLoading = true)
|
||||||
|
public class LoadANonExistingNotFoundLazyBatchEntityTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
private static final int NUMBER_OF_ENTITIES = 20;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-11147")
|
||||||
|
public void loadEntityWithNotFoundAssociation() {
|
||||||
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
inTransaction( (session) -> {
|
||||||
|
List<Employee> employees = new ArrayList<>( NUMBER_OF_ENTITIES );
|
||||||
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
|
employees.add( session.load( Employee.class, i + 1 ) );
|
||||||
|
}
|
||||||
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
|
Hibernate.initialize( employees.get( i ) );
|
||||||
|
assertNull( employees.get( i ).employer );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// A "not found" association cannot be batch fetched because
|
||||||
|
// Employee#employer must be initialized immediately.
|
||||||
|
// Enhanced proxies (and HibernateProxy objects) should never be created
|
||||||
|
// for a "not found" association.
|
||||||
|
assertEquals( 2 * NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-11147")
|
||||||
|
public void getEntityWithNotFoundAssociation() {
|
||||||
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
inTransaction( (session) -> {
|
||||||
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
|
assertNull( employee.employer );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// A "not found" association cannot be batch fetched because
|
||||||
|
// Employee#employer must be initialized immediately.
|
||||||
|
// Enhanced proxies (and HibernateProxy objects) should never be created
|
||||||
|
// for a "not found" association.
|
||||||
|
assertEquals( 2 * NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-11147")
|
||||||
|
public void updateNotFoundAssociationWithNew() {
|
||||||
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
inTransaction( (session) -> {
|
||||||
|
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
||||||
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
|
Employer employer = new Employer();
|
||||||
|
employer.id = 2 * employee.id;
|
||||||
|
employer.name = "Employer #" + employer.id;
|
||||||
|
employee.employer = employer;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
inTransaction( (session) -> {
|
||||||
|
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
||||||
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
|
assertTrue( Hibernate.isInitialized( employee.employer ) );
|
||||||
|
assertEquals( employee.id * 2, employee.employer.id );
|
||||||
|
assertEquals( "Employer #" + employee.employer.id, employee.employer.name );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[] {
|
||||||
|
Employee.class,
|
||||||
|
Employer.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpData() {
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
||||||
|
final Employee employee = new Employee();
|
||||||
|
employee.id = i + 1;
|
||||||
|
employee.name = "Employee #" + employee.id;
|
||||||
|
session.persist( employee );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
// Add "not found" associations
|
||||||
|
session.createNativeQuery( "update Employee set employer_id = id" ).executeUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanupDate() {
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
session.createQuery( "delete from Employee" ).executeUpdate();
|
||||||
|
session.createQuery( "delete from Employer" ).executeUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
|
||||||
|
super.configureStandardServiceRegistryBuilder( ssrb );
|
||||||
|
ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" );
|
||||||
|
ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) {
|
||||||
|
super.configureSessionFactoryBuilder( sfb );
|
||||||
|
sfb.applyStatisticsSupport( true );
|
||||||
|
sfb.applySecondLevelCacheSupport( false );
|
||||||
|
sfb.applyQueryCacheSupport( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employee")
|
||||||
|
public static class Employee {
|
||||||
|
@Id
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "employer_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
|
||||||
|
@NotFound(action=NotFoundAction.IGNORE)
|
||||||
|
private Employer employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employer")
|
||||||
|
@BatchSize(size = 10)
|
||||||
|
public static class Employer {
|
||||||
|
@Id
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,10 @@
|
||||||
package org.hibernate.orm.test.inheritance;
|
package org.hibernate.orm.test.inheritance;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.ConstraintMode;
|
import jakarta.persistence.ConstraintMode;
|
||||||
import jakarta.persistence.DiscriminatorColumn;
|
import jakarta.persistence.DiscriminatorColumn;
|
||||||
|
@ -303,6 +307,7 @@ public class TransientOverrideAsPersistentMappedSuperclass {
|
||||||
// Editor#title (which uses the same e_title column) can be non-null,
|
// Editor#title (which uses the same e_title column) can be non-null,
|
||||||
// and there is no associated group.
|
// and there is no associated group.
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
public Group getGroup() {
|
public Group getGroup() {
|
||||||
return group;
|
return group;
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
package org.hibernate.orm.test.inheritance;
|
package org.hibernate.orm.test.inheritance;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.ConstraintMode;
|
import jakarta.persistence.ConstraintMode;
|
||||||
import jakarta.persistence.DiscriminatorColumn;
|
import jakarta.persistence.DiscriminatorColumn;
|
||||||
|
@ -296,6 +300,7 @@ public class TransientOverrideAsPersistentSingleTable {
|
||||||
// Editor#title (which uses the same e_title column) can be non-null,
|
// Editor#title (which uses the same e_title column) can be non-null,
|
||||||
// and there is no associated group.
|
// and there is no associated group.
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
public Group getGroup() {
|
public Group getGroup() {
|
||||||
return group;
|
return group;
|
||||||
|
|
|
@ -8,6 +8,10 @@ package org.hibernate.orm.test.inheritance;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.ConstraintMode;
|
import jakarta.persistence.ConstraintMode;
|
||||||
import jakarta.persistence.DiscriminatorColumn;
|
import jakarta.persistence.DiscriminatorColumn;
|
||||||
|
@ -299,6 +303,7 @@ public class TransientOverrideAsPersistentTablePerClass {
|
||||||
// Editor#title (which uses the same e_title column) can be non-null,
|
// Editor#title (which uses the same e_title column) can be non-null,
|
||||||
// and there is no associated group.
|
// and there is no associated group.
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
public Group getGroup() {
|
public Group getGroup() {
|
||||||
return group;
|
return group;
|
||||||
|
|
|
@ -196,6 +196,40 @@ public class EagerProxyNotFoundTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetEmployeeWithNotExistingAssociation(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
final Employee employee = new Employee();
|
||||||
|
employee.id = 1;
|
||||||
|
session.persist( employee );
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
|
||||||
|
session.createNativeQuery( "update Employee set locationId = 3 where id = 1" )
|
||||||
|
.executeUpdate();
|
||||||
|
} );
|
||||||
|
try {
|
||||||
|
scope.inTransaction( session -> session.get( Employee.class, 1 ) );
|
||||||
|
fail( "EntityNotFoundException should have been thrown because Employee.location is not found " +
|
||||||
|
"and is not mapped with @NotFound(IGNORE)" );
|
||||||
|
}
|
||||||
|
catch (EntityNotFoundException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// also test explicit join
|
||||||
|
try {
|
||||||
|
scope.inTransaction( session -> session.createQuery(
|
||||||
|
"from Employee e left join e.location ",
|
||||||
|
Employee.class
|
||||||
|
).getSingleResult() );
|
||||||
|
fail( "EntityNotFoundException should have been thrown because Employee.location is not found " +
|
||||||
|
"and is not mapped with @NotFound(IGNORE)" );
|
||||||
|
}
|
||||||
|
catch (EntityNotFoundException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExistingProxyWithNoAssociation(SessionFactoryScope scope) {
|
public void testExistingProxyWithNoAssociation(SessionFactoryScope scope) {
|
||||||
scope.inTransaction(
|
scope.inTransaction(
|
||||||
|
|
|
@ -9,20 +9,12 @@ package org.hibernate.orm.test.notfound;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.OneToOne;
|
|
||||||
|
|
||||||
import org.hibernate.annotations.NotFound;
|
import org.hibernate.annotations.NotFound;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.query.SemanticException;
|
|
||||||
import org.hibernate.query.sqm.ParsingException;
|
import org.hibernate.query.sqm.ParsingException;
|
||||||
|
|
||||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
|
||||||
import org.hibernate.testing.orm.junit.JiraKey;
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
@ -30,6 +22,12 @@ import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,11 +42,6 @@ public class FkRefTests {
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15099" )
|
@JiraKey( "HHH-15099" )
|
||||||
@JiraKey( "HHH-15106" )
|
@JiraKey( "HHH-15106" )
|
||||||
@FailureExpected(
|
|
||||||
reason = "Coin is selected and so its currency needs to be fetched. At the " +
|
|
||||||
"moment, that fetch always happens via a join-fetch. Ideally we'd support " +
|
|
||||||
"loading these via subsequent-select also"
|
|
||||||
)
|
|
||||||
public void testSimplePredicateUse(SessionFactoryScope scope) {
|
public void testSimplePredicateUse(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
@ -160,11 +153,6 @@ public class FkRefTests {
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15099" )
|
@JiraKey( "HHH-15099" )
|
||||||
@JiraKey( "HHH-15106" )
|
@JiraKey( "HHH-15106" )
|
||||||
@FailureExpected(
|
|
||||||
reason = "Coin is selected and so its currency needs to be fetched. At the " +
|
|
||||||
"moment, that fetch always happens via a join-fetch. Ideally we'd support " +
|
|
||||||
"loading these via subsequent-select also"
|
|
||||||
)
|
|
||||||
public void testNullnessPredicateUse2(SessionFactoryScope scope) {
|
public void testNullnessPredicateUse2(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
|
|
@ -62,8 +62,7 @@ public class NotFoundAndSelectJoinTest {
|
||||||
assertThat( person ).isNotNull();
|
assertThat( person ).isNotNull();
|
||||||
assertThat( Hibernate.isInitialized( person ) );
|
assertThat( Hibernate.isInitialized( person ) );
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 );
|
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 2 );
|
||||||
assertThat( statementInspector.getNumberOfJoins( 0 ) ).isEqualTo( 1 );
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,7 +224,7 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
assertThat( coins ).hasSize( 1 );
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
@ -333,7 +333,7 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OneToOne(fetch = FetchType.LAZY)
|
@OneToOne(fetch = FetchType.EAGER)
|
||||||
@NotFound(action = NotFoundAction.EXCEPTION)
|
@NotFound(action = NotFoundAction.EXCEPTION)
|
||||||
public Currency getCurrency() {
|
public Currency getCurrency() {
|
||||||
return currency;
|
return currency;
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
* 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
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.hibernate.orm.test.query.sql;
|
package org.hibernate.orm.test.query.sql;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.FetchNotFoundException;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
import org.hibernate.testing.orm.junit.JiraKey;
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
|
@ -24,8 +26,10 @@ import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,10 +48,16 @@ public class NativeQueryEagerAssociationTest {
|
||||||
final Building building1 = new Building( 1L, "building_1" );
|
final Building building1 = new Building( 1L, "building_1" );
|
||||||
final Building building2 = new Building( 2L, "building_2" );
|
final Building building2 = new Building( 2L, "building_2" );
|
||||||
final Building building3 = new Building( 3L, "building_3" );
|
final Building building3 = new Building( 3L, "building_3" );
|
||||||
|
final Building building4 = new Building( 4L, "building_4" );
|
||||||
session.persist( building1 );
|
session.persist( building1 );
|
||||||
session.persist( building2 );
|
session.persist( building2 );
|
||||||
session.persist( building3 );
|
session.persist( building3 );
|
||||||
session.persist( new Classroom( 1L, "classroom_1", building1, List.of( building2, building3 ) ) );
|
session.persist( new Classroom( 1L, "classroom_1", building1, List.of( building2, building3 ) ) );
|
||||||
|
session.persist( new Classroom( 2L, "classroom_2", building4, null ) );
|
||||||
|
} );
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
// delete associated entity to trigger @NotFound
|
||||||
|
session.createMutationQuery( "delete from Building where id = 4L" ).executeUpdate();
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,16 +72,27 @@ public class NativeQueryEagerAssociationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testNativeQuery(SessionFactoryScope scope) {
|
public void testNativeQuery(SessionFactoryScope scope) {
|
||||||
final Classroom result = scope.fromTransaction(
|
final Classroom result = scope.fromTransaction(
|
||||||
session -> session.createNativeQuery( "select id, description, building_id from classroom", Classroom.class ).getSingleResult()
|
session -> session.createNativeQuery( "select * from Classroom where id = 1", Classroom.class )
|
||||||
|
.getSingleResult()
|
||||||
);
|
);
|
||||||
assertEquals( 1L, result.getId() );
|
assertEquals( 1L, result.getId() );
|
||||||
assertTrue( Hibernate.isInitialized( result.getBuilding() ) );
|
assertTrue( Hibernate.isInitialized( result.getBuilding() ) );
|
||||||
assertTrue( Hibernate.isInitialized( result.getAdjacentBuildings() ) );
|
assertTrue( Hibernate.isInitialized( result.getAdjacentBuildings() ) );
|
||||||
assertEquals( 1L, result.getBuilding().getId() );
|
assertEquals( 1L, result.getBuilding().getId() );
|
||||||
|
assertEquals( "building_1", result.getBuilding().getDescription() );
|
||||||
assertEquals( 2, result.getAdjacentBuildings().size() );
|
assertEquals( 2, result.getAdjacentBuildings().size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNativeQueryNotFound(SessionFactoryScope scope) {
|
||||||
|
assertThrows( FetchNotFoundException.class, () -> scope.inTransaction(
|
||||||
|
session -> session.createNativeQuery( "select * from Classroom where id = 2", Classroom.class )
|
||||||
|
.getSingleResult()
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
@Entity(name = "Building")
|
@Entity(name = "Building")
|
||||||
|
@Table(name = "Building")
|
||||||
public static class Building {
|
public static class Building {
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
|
@ -96,6 +117,7 @@ public class NativeQueryEagerAssociationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(name = "Classroom")
|
@Entity(name = "Classroom")
|
||||||
|
@Table(name = "Classroom")
|
||||||
public static class Classroom {
|
public static class Classroom {
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
|
@ -103,6 +125,7 @@ public class NativeQueryEagerAssociationTest {
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
@NotFound(action = NotFoundAction.EXCEPTION)
|
||||||
private Building building;
|
private Building building;
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.EAGER)
|
@OneToMany(fetch = FetchType.EAGER)
|
||||||
|
|
Loading…
Reference in New Issue