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();
|
||||
isInternalLoadNullable = ( isNullable && bootValue.isForeignKeyEnabled() ) || notFoundAction == NotFoundAction.IGNORE;
|
||||
isInternalLoadNullable = ( isNullable && bootValue.isForeignKeyEnabled() ) || hasNotFoundAction();
|
||||
}
|
||||
else {
|
||||
assert bootValue instanceof OneToOne;
|
||||
|
@ -1300,14 +1300,13 @@ public class ToOneAttributeMapping
|
|||
&& 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 :
|
||||
public static class EntityA {
|
||||
...
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
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)
|
||||
*/
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE && selected || hasNotFoundAction() ) {
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE && selected ) {
|
||||
final TableGroup tableGroup = determineTableGroupForFetch(
|
||||
fetchablePath,
|
||||
fetchParent,
|
||||
|
@ -1336,9 +1335,11 @@ public class ToOneAttributeMapping
|
|||
|
||||
return withRegisteredAssociationKeys(
|
||||
() -> {
|
||||
final DomainResult<?> keyResult;
|
||||
if ( notFoundAction != null ) {
|
||||
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||
DomainResult<?> keyResult = null;
|
||||
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(
|
||||
fetchablePath,
|
||||
parentTableGroup,
|
||||
|
@ -1346,17 +1347,15 @@ public class ToOneAttributeMapping
|
|||
creationState
|
||||
);
|
||||
}
|
||||
else {
|
||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||
fetchablePath,
|
||||
parentTableGroup,
|
||||
fetchParent,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
keyResult = null;
|
||||
else if ( notFoundAction != null ) {
|
||||
// For the target side only add keyResult when a not-found action is present
|
||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||
fetchablePath,
|
||||
parentTableGroup,
|
||||
fetchParent,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
|
||||
return new EntityFetchJoinedImpl(
|
||||
|
@ -1364,7 +1363,8 @@ public class ToOneAttributeMapping
|
|||
this,
|
||||
tableGroup,
|
||||
keyResult,
|
||||
fetchablePath,creationState
|
||||
fetchablePath,
|
||||
creationState
|
||||
);
|
||||
},
|
||||
creationState
|
||||
|
@ -1414,7 +1414,8 @@ public class ToOneAttributeMapping
|
|||
);
|
||||
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(
|
||||
fetchParent,
|
||||
this,
|
||||
|
|
|
@ -77,11 +77,11 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
|
|||
// need to also look at the foreign-key value column to check
|
||||
// for a dangling foreign-key
|
||||
|
||||
if ( notFoundAction != null && keyAssembler != null ) {
|
||||
if ( keyAssembler != null ) {
|
||||
final Object fkKeyValue = keyAssembler.assemble( rowProcessingState );
|
||||
if ( fkKeyValue != null ) {
|
||||
if ( isMissing() ) {
|
||||
if ( notFoundAction == NotFoundAction.EXCEPTION ) {
|
||||
if ( notFoundAction != NotFoundAction.IGNORE ) {
|
||||
throw new FetchNotFoundException(
|
||||
referencedFetchable.getEntityMappingType().getEntityName(),
|
||||
fkKeyValue
|
||||
|
|
|
@ -115,9 +115,8 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
|||
|
||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||
|
||||
// there should be 1 SQL statement with a join executed
|
||||
assertThat( paramterCounts ).hasSize( 1 );
|
||||
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
||||
// there should be 5 SQL statements executed
|
||||
assertThat( paramterCounts ).hasSize( 5 );
|
||||
|
||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||
for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) {
|
||||
|
@ -158,9 +157,8 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
|||
|
||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||
|
||||
// there should be 1 SQL statement with a join executed
|
||||
assertThat( paramterCounts ).hasSize( 1 );
|
||||
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
||||
// there should be 8 SQL statements executed
|
||||
assertThat( paramterCounts ).hasSize( 8 );
|
||||
|
||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||
|
||||
|
|
|
@ -87,8 +87,8 @@ public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase {
|
|||
|
||||
// `@NotFound` forces EAGER join fetching
|
||||
assertThat( sqlInterceptor.getQueryCount() ).
|
||||
describedAs( "Expecting 1 query (w/ join) due to `@NotFound`" )
|
||||
.isEqualTo( 1 );
|
||||
describedAs( "Expecting 2 queries due to `@NotFound`" )
|
||||
.isEqualTo( 2 );
|
||||
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
|
||||
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
|
||||
.isTrue();
|
||||
|
|
|
@ -76,8 +76,7 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
|||
}
|
||||
} );
|
||||
|
||||
// not-found associations are always join-fetched, so we should
|
||||
// get `NUMBER_OF_ENTITIES` queries
|
||||
// we should get `NUMBER_OF_ENTITIES` queries
|
||||
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
|
||||
// get `NUMBER_OF_ENTITIES` queries
|
||||
assertThat( statistics.getPrepareStatementCount() ).isEqualTo( NUMBER_OF_ENTITIES );
|
||||
// we should get `NUMBER_OF_ENTITIES` queries
|
||||
assertEquals( NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -187,7 +185,7 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
|||
|
||||
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))
|
||||
@NotFound(action=NotFoundAction.IGNORE)
|
||||
private Employer employer;
|
||||
|
|
|
@ -67,9 +67,10 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
|||
}
|
||||
);
|
||||
|
||||
// not-found associations are always join-fetched, so we should
|
||||
// get 1 query for the Employee with join
|
||||
assertEquals( 1, statistics.getPrepareStatementCount() );
|
||||
// The Employee#employer must be initialized immediately because
|
||||
// enhanced proxies (and HibernateProxy objects) should never be created
|
||||
// for a "not found" association.
|
||||
assertEquals( 2, statistics.getPrepareStatementCount() );
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -85,9 +86,10 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
|||
}
|
||||
);
|
||||
|
||||
// not-found associations are always join-fetched, so we should
|
||||
// get 1 query for the Employee with join
|
||||
assertEquals( 1, statistics.getPrepareStatementCount() );
|
||||
// The Employee#employer must be initialized immediately because
|
||||
// enhanced proxies (and HibernateProxy objects) should never be created
|
||||
// for a "not found" association.
|
||||
assertEquals( 2, statistics.getPrepareStatementCount() );
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
|
@ -303,6 +307,7 @@ public class TransientOverrideAsPersistentMappedSuperclass {
|
|||
// Editor#title (which uses the same e_title column) can be non-null,
|
||||
// and there is no associated group.
|
||||
@ManyToOne(optional = false)
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
package org.hibernate.orm.test.inheritance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
|
@ -296,6 +300,7 @@ public class TransientOverrideAsPersistentSingleTable {
|
|||
// Editor#title (which uses the same e_title column) can be non-null,
|
||||
// and there is no associated group.
|
||||
@ManyToOne(optional = false)
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
|
|
|
@ -8,6 +8,10 @@ package org.hibernate.orm.test.inheritance;
|
|||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
|
@ -299,6 +303,7 @@ public class TransientOverrideAsPersistentTablePerClass {
|
|||
// Editor#title (which uses the same e_title column) can be non-null,
|
||||
// and there is no associated group.
|
||||
@ManyToOne(optional = false)
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@JoinColumn(name = "e_title", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
public Group getGroup() {
|
||||
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
|
||||
public void testExistingProxyWithNoAssociation(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
|
|
@ -9,20 +9,12 @@ package org.hibernate.orm.test.notfound;
|
|||
import java.io.Serializable;
|
||||
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.NotFoundAction;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.sqm.ParsingException;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
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.SessionFactory;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
|
@ -44,11 +42,6 @@ public class FkRefTests {
|
|||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
@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) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -160,11 +153,6 @@ public class FkRefTests {
|
|||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
@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) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
|
|
@ -62,8 +62,7 @@ public class NotFoundAndSelectJoinTest {
|
|||
assertThat( person ).isNotNull();
|
||||
assertThat( Hibernate.isInitialized( person ) );
|
||||
|
||||
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 );
|
||||
assertThat( statementInspector.getNumberOfJoins( 0 ) ).isEqualTo( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 2 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -224,7 +224,7 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
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( " Currency " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
|
@ -333,7 +333,7 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@OneToOne(fetch = FetchType.EAGER)
|
||||
@NotFound(action = NotFoundAction.EXCEPTION)
|
||||
public Currency getCurrency() {
|
||||
return currency;
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
* 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.query.sql;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.FetchNotFoundException;
|
||||
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.JiraKey;
|
||||
|
@ -24,8 +26,10 @@ import jakarta.persistence.FetchType;
|
|||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
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 building2 = new Building( 2L, "building_2" );
|
||||
final Building building3 = new Building( 3L, "building_3" );
|
||||
final Building building4 = new Building( 4L, "building_4" );
|
||||
session.persist( building1 );
|
||||
session.persist( building2 );
|
||||
session.persist( 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
|
||||
public void testNativeQuery(SessionFactoryScope scope) {
|
||||
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() );
|
||||
assertTrue( Hibernate.isInitialized( result.getBuilding() ) );
|
||||
assertTrue( Hibernate.isInitialized( result.getAdjacentBuildings() ) );
|
||||
assertEquals( 1L, result.getBuilding().getId() );
|
||||
assertEquals( "building_1", result.getBuilding().getDescription() );
|
||||
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")
|
||||
@Table(name = "Building")
|
||||
public static class Building {
|
||||
@Id
|
||||
private Long id;
|
||||
|
@ -96,6 +117,7 @@ public class NativeQueryEagerAssociationTest {
|
|||
}
|
||||
|
||||
@Entity(name = "Classroom")
|
||||
@Table(name = "Classroom")
|
||||
public static class Classroom {
|
||||
@Id
|
||||
private Long id;
|
||||
|
@ -103,6 +125,7 @@ public class NativeQueryEagerAssociationTest {
|
|||
private String description;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@NotFound(action = NotFoundAction.EXCEPTION)
|
||||
private Building building;
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER)
|
||||
|
|
Loading…
Reference in New Issue