HHH-16043 Add test for issue
This commit is contained in:
parent
2d7774b9ab
commit
17506b7f80
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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.batchfetch;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.MappedSuperclass;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OrderBy;
|
||||||
|
|
||||||
|
import static jakarta.persistence.CascadeType.ALL;
|
||||||
|
import static jakarta.persistence.FetchType.LAZY;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Marco Belladelli
|
||||||
|
*/
|
||||||
|
@DomainModel(
|
||||||
|
annotatedClasses = {
|
||||||
|
NestedLazyManyToOneTest.AbstractEntity.class,
|
||||||
|
NestedLazyManyToOneTest.Entity1.class,
|
||||||
|
NestedLazyManyToOneTest.Entity2.class,
|
||||||
|
NestedLazyManyToOneTest.Entity3.class
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
@ServiceRegistry(settings = {
|
||||||
|
@Setting(name = AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, value = "5")
|
||||||
|
})
|
||||||
|
@JiraKey("HHH-16043")
|
||||||
|
public class NestedLazyManyToOneTest {
|
||||||
|
@BeforeAll
|
||||||
|
public void prepareData(SessionFactoryScope scope) {
|
||||||
|
final Entity1 entity1 = new Entity1();
|
||||||
|
entity1.setId( "0" );
|
||||||
|
|
||||||
|
final Set<Entity2> entities2 = new HashSet<>();
|
||||||
|
for ( int i = 0; i < 8; i++ ) {
|
||||||
|
final Entity2 entity2 = new Entity2();
|
||||||
|
entity2.setId( entity1.getId() + "_" + i );
|
||||||
|
entity2.setParent( entity1 );
|
||||||
|
entities2.add( entity2 );
|
||||||
|
|
||||||
|
// add nested children only to first and last entity
|
||||||
|
if ( i == 0 || i == 7 ) {
|
||||||
|
final Set<Entity3> entities3 = new HashSet<>();
|
||||||
|
for ( int j = 0; j < 5; j++ ) {
|
||||||
|
final Entity3 entity3 = new Entity3();
|
||||||
|
entity3.setId( entity2.getId() + "_" + j );
|
||||||
|
entity3.setParent( entity2 );
|
||||||
|
entities3.add( entity3 );
|
||||||
|
}
|
||||||
|
entity2.setChildren( entities3 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entity1.setChildren( entities2 );
|
||||||
|
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
session.persist( entity1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFirstLevelChildren(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
Entity1 fromDb = session.find( Entity1.class, "0" );
|
||||||
|
Set<Entity2> children = fromDb.getChildren();
|
||||||
|
|
||||||
|
assertEquals( 8, children.size() );
|
||||||
|
statementInspector.assertExecutedCount( 2 );
|
||||||
|
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "\\?", 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNestedChildrenLessThanBatchSize(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
Entity1 entity1 = session.find( Entity1.class, "0" );
|
||||||
|
int i = 0;
|
||||||
|
for ( Entity2 child2 : entity1.getChildren() ) {
|
||||||
|
// get only first 5 (< batch size) elements
|
||||||
|
// this doesn't trigger an additional query only because entity1.children
|
||||||
|
// are ordered with @OrderBy, and we always get the first 5 first
|
||||||
|
if ( i++ >= 5 ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Set<Entity3> children3 = child2.getChildren();
|
||||||
|
if ( child2.getId().equals( "0_0" ) ) {
|
||||||
|
assertEquals( 5, children3.size() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEquals( 0, children3.size() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( 8, entity1.getChildren().size() );
|
||||||
|
statementInspector.assertExecutedCount( 3 ); // 1 for Entity1, 1 for Entity2, 1 for Entity3
|
||||||
|
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 2, "\\?", 5 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNestedChildrenMoreThanBatchSize(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
Entity1 entity1 = session.find( Entity1.class, "0" );
|
||||||
|
for ( Entity2 child2 : entity1.getChildren() ) {
|
||||||
|
Set<Entity3> children3 = child2.getChildren();
|
||||||
|
if ( child2.getId().equals( "0_0" ) || child2.getId().equals( "0_7" ) ) {
|
||||||
|
assertEquals( 5, children3.size() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEquals( 0, children3.size() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( 8, entity1.getChildren().size() );
|
||||||
|
statementInspector.assertExecutedCount( 4 ); // 1 for Entity1, 1 for Entity2, 2 for Entity3
|
||||||
|
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 2, "\\?", 5 );
|
||||||
|
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 3, "\\?", 3 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@MappedSuperclass
|
||||||
|
public static class AbstractEntity {
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "#" + getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Entity(name = "Entity1")
|
||||||
|
public static class Entity1 extends AbstractEntity {
|
||||||
|
@OneToMany(mappedBy = "parent", cascade = ALL, orphanRemoval = true)
|
||||||
|
@OrderBy("id")
|
||||||
|
private Set<Entity2> children = new HashSet<>();
|
||||||
|
|
||||||
|
public Set<Entity2> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(Set<Entity2> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Entity2")
|
||||||
|
public static class Entity2 extends AbstractEntity {
|
||||||
|
@ManyToOne(fetch = LAZY)
|
||||||
|
private Entity1 parent;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "parent", cascade = ALL, orphanRemoval = true)
|
||||||
|
private Set<Entity3> children = new HashSet<>();
|
||||||
|
|
||||||
|
public Entity1 getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(Entity1 parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Entity3> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(Set<Entity3> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Entity3")
|
||||||
|
public static class Entity3 extends AbstractEntity {
|
||||||
|
@ManyToOne(fetch = LAZY)
|
||||||
|
private Entity2 parent;
|
||||||
|
|
||||||
|
public Entity2 getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(Entity2 parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,8 +93,12 @@ public class SQLStatementInspector implements StatementInspector {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assertNumberOfOccurrenceInQuery(int queryNumber, String toCheck, int expectedNumberOfOccurrences) {
|
public void assertNumberOfOccurrenceInQuery(int queryNumber, String toCheck, int expectedNumberOfOccurrences) {
|
||||||
|
assertNumberOfOccurrenceInQueryNoSpace( queryNumber, " " + toCheck + " ", expectedNumberOfOccurrences );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertNumberOfOccurrenceInQueryNoSpace(int queryNumber, String toCheck, int expectedNumberOfOccurrences) {
|
||||||
String query = sqlQueries.get( queryNumber );
|
String query = sqlQueries.get( queryNumber );
|
||||||
int actual = query.split( " " + toCheck + " ", -1 ).length - 1;
|
int actual = query.split( toCheck, -1 ).length - 1;
|
||||||
assertThat( "number of " + toCheck, actual, is( expectedNumberOfOccurrences ) );
|
assertThat( "number of " + toCheck, actual, is( expectedNumberOfOccurrences ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue