HHH-15099 - Improve handling of associations marked with @NotFound
- Tests
This commit is contained in:
parent
bdf8b2fc2e
commit
de97e8e1a4
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.orm.test.notfound.exception;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
|
@ -17,6 +18,7 @@ import org.hibernate.ObjectNotFoundException;
|
|||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
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;
|
||||
|
@ -42,13 +44,13 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = { NotFoundExceptionLogicalOneToOneTest.Coin.class, NotFoundExceptionLogicalOneToOneTest.Currency.class } )
|
||||
@SessionFactory
|
||||
@SessionFactory( useCollectingStatementInspector = true )
|
||||
public class NotFoundExceptionLogicalOneToOneTest {
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
public void testProxy(SessionFactoryScope scope) {
|
||||
// test handling of a proxy for the missing Coin
|
||||
scope.inTransaction( (session) -> {
|
||||
// the non-existent Child
|
||||
final Currency proxy = session.byId( Currency.class ).getReference( 1 );
|
||||
try {
|
||||
Hibernate.initialize( proxy );
|
||||
|
@ -63,11 +65,20 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " +
|
||||
"when we reference the proxy, but thats not correct"
|
||||
)
|
||||
@FailureExpected( reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1." )
|
||||
public void testGet(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
session.get( Coin.class, 2 );
|
||||
|
||||
// here we assume join, but this could use subsequent-select instead
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
try {
|
||||
session.get( Coin.class, 1 );
|
||||
|
@ -82,15 +93,43 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " +
|
||||
"when we reference the proxy, but thats not correct"
|
||||
)
|
||||
@FailureExpected( reason = "We return a proxy for `Coin#currency`, which violates NOTE #1." )
|
||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).isEmpty();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
} );
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id = 2";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected( reason = "We return a proxy for `Coin#currency`, which violates NOTE #1." )
|
||||
public void testQueryOwnerSelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.id = 1";
|
||||
try {
|
||||
session.createQuery( hql, Coin.class ).getResultList();
|
||||
//noinspection unused (debugging)
|
||||
final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult();
|
||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
||||
}
|
||||
catch (ObjectNotFoundException expected) {
|
||||
|
@ -98,25 +137,12 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " +
|
||||
"when we reference the proxy, but thats not correct"
|
||||
)
|
||||
public void testQueryOwnerSelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c";
|
||||
try {
|
||||
session.createQuery( hql, Coin.class ).getResultList();
|
||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
||||
}
|
||||
catch (ObjectNotFoundException expected) {
|
||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
}
|
||||
final String hql = "select c from Coin c where c.id = 2";
|
||||
final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult();
|
||||
assertThat( Hibernate.isPropertyInitialized( coin, "currency" ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( coin.getCurrency() ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -125,9 +151,13 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
scope.inTransaction( (session) -> {
|
||||
Currency euro = new Currency( 1, "Euro" );
|
||||
Coin fiveC = new Coin( 1, "Five cents", euro );
|
||||
|
||||
session.persist( euro );
|
||||
session.persist( fiveC );
|
||||
|
||||
Currency usd = new Currency( 2, "USD" );
|
||||
Coin penny = new Coin( 2, "Penny", usd );
|
||||
session.persist( usd );
|
||||
session.persist( penny );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
|
@ -138,7 +168,8 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
|||
@AfterEach
|
||||
public void cleanupTest(SessionFactoryScope scope) throws Exception {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createMutationQuery( "delete Coin where id = 1" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Coin" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Currency" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.orm.test.notfound.exception;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
|
@ -66,8 +67,7 @@ public class NotFoundExceptionManyToOneTest {
|
|||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "ObjectNotFoundException is thrown but caught and null is returned - see " +
|
||||
"org.hibernate.internal.SessionImpl.IdentifierLoadAccessImpl#doLoad"
|
||||
reason = "ObjectNotFoundException is thrown, but caught in `IdentifierLoadAccessImpl#doLoad` and null returned instead"
|
||||
)
|
||||
public void testGet(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
@ -94,26 +94,28 @@ public class NotFoundExceptionManyToOneTest {
|
|||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "EntityNotFoundException thrown rather than ObjectNotFoundException; " +
|
||||
"ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException"
|
||||
reason = "Does not do the join. Instead selects the Coin based on `currency_id` and then " +
|
||||
"subsequent-selects the Currency. Ultimately results in a `Coin#1` reference with a " +
|
||||
"null Currency."
|
||||
)
|
||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||
try {
|
||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||
session.createQuery( hql, Coin.class ).getResultList();
|
||||
|
||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
||||
}
|
||||
catch (ObjectNotFoundException expected) {
|
||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
|
||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
@ -121,27 +123,28 @@ public class NotFoundExceptionManyToOneTest {
|
|||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected(
|
||||
reason = "EntityNotFoundException thrown rather than ObjectNotFoundException; " +
|
||||
"ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException"
|
||||
reason = "Does not do the join. Instead selects the Coin based on `currency_id` and then " +
|
||||
"subsequent-selects the Currency. Ultimately results in a `Coin#1` reference with a " +
|
||||
"null Currency."
|
||||
)
|
||||
public void testQueryOwnerSelection(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c";
|
||||
final String hql = "select c from Coin c where c.id = 1";
|
||||
try {
|
||||
session.createQuery( hql, Coin.class ).getResultList();
|
||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
||||
}
|
||||
catch (ObjectNotFoundException expected) {
|
||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
|
||||
// technically we could use a subsequent-select rather than a join...
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
|
||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
@ -156,7 +159,7 @@ public class NotFoundExceptionManyToOneTest {
|
|||
// - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice
|
||||
// - the other way to look at it is that there are simply no matching results, so nothing to return
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c.currency from Coin c";
|
||||
final String hql = "select c.currency from Coin c where c.id = 1";
|
||||
try {
|
||||
session.createQuery( hql, Currency.class ).getResultList();
|
||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
||||
|
@ -173,9 +176,13 @@ public class NotFoundExceptionManyToOneTest {
|
|||
scope.inTransaction( (session) -> {
|
||||
Currency euro = new Currency( 1, "Euro" );
|
||||
Coin fiveC = new Coin( 1, "Five cents", euro );
|
||||
|
||||
session.persist( euro );
|
||||
session.persist( fiveC );
|
||||
|
||||
Currency usd = new Currency( 2, "USD" );
|
||||
Coin penny = new Coin( 2, "Penny", usd );
|
||||
session.persist( usd );
|
||||
session.persist( penny );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
|
@ -184,9 +191,10 @@ public class NotFoundExceptionManyToOneTest {
|
|||
}
|
||||
|
||||
@AfterEach
|
||||
protected void dropTestData(SessionFactoryScope scope) {
|
||||
public void cleanupTest(SessionFactoryScope scope) throws Exception {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createMutationQuery( "delete Coin where id = 1" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Coin" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Currency" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import jakarta.persistence.Entity;
|
|||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Tuple;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
|
@ -83,7 +84,6 @@ public class NotFoundIgnoreManyToOneTest {
|
|||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected( reason = "Bad results due to cross-join" )
|
||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -91,8 +91,7 @@ public class NotFoundIgnoreManyToOneTest {
|
|||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||
final List<Coin> coins = session.createSelectionQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||
assertThat( coins ).isEmpty();
|
||||
|
||||
// technically we could use a subsequent-select rather than a join...
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
|
@ -134,6 +133,22 @@ public class NotFoundIgnoreManyToOneTest {
|
|||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c.id, c.currency from Coin c";
|
||||
final List<Tuple> tuples = session.createSelectionQuery( hql, Tuple.class ).getResultList();
|
||||
assertThat( tuples ).hasSize( 1 );
|
||||
final Tuple tuple = tuples.get( 0 );
|
||||
assertThat( tuple.get( 0 ) ).isEqualTo( 1 );
|
||||
assertThat( tuple.get( 1 ) ).isNull();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
} );
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// I guess this one is somewhat debatable, but for consistency I think this makes the most sense
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c.currency from Coin c";
|
||||
session.createQuery( hql, Currency.class ).getResultList();
|
||||
|
|
|
@ -12,6 +12,7 @@ import jakarta.persistence.Entity;
|
|||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Tuple;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
|
@ -83,7 +84,7 @@ public class NotFoundIgnoreOneToOneTest {
|
|||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected( reason = "Bad results due to cross-join" )
|
||||
@FailureExpected( reason = "Bad results due to join" )
|
||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
@ -127,11 +128,19 @@ public class NotFoundIgnoreOneToOneTest {
|
|||
|
||||
@Test
|
||||
@JiraKey( "HHH-15060" )
|
||||
@FailureExpected( reason = "Has zero results because of inner-join; & the select w/ inner-join is executed twice for some odd reason" )
|
||||
@FailureExpected( reason = "Has zero results because of join; & the select w/ join is executed twice for some yet-unknown reason" )
|
||||
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c.id, c.currency from Coin c";
|
||||
final List<Tuple> tuples = session.createQuery( hql, Tuple.class ).getResultList();
|
||||
assertThat( tuples ).hasSize( 1 );
|
||||
final Tuple tuple = tuples.get( 0 );
|
||||
assertThat( tuple.get( 0 ) ).isEqualTo( 1 );
|
||||
assertThat( tuple.get( 1 ) ).isNull();
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c.currency from Coin c";
|
||||
session.createQuery( hql, Currency.class ).getResultList();
|
||||
final List<Currency> currencies = session.createQuery( hql, Currency.class ).getResultList();
|
||||
assertThat( currencies ).hasSize( 1 );
|
||||
assertThat( currencies.get( 0 ) ).isNull();
|
||||
|
|
Loading…
Reference in New Issue