HHH-15473 Copy/paste a few select tests to set collectionInDefaultFetchGroup to false explicitly

This commit is contained in:
Yoann Rodière 2022-09-05 09:19:52 +02:00 committed by Sanne Grinovero
parent 16f865f100
commit 560722dfaa
6 changed files with 1585 additions and 0 deletions

View File

@ -0,0 +1,281 @@
/*
* 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.cascade;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
/**
* Same as {@link CascadeDeleteCollectionTest},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*
* @author Luis Barreiro
*/
@TestForIssue( jiraKey = "HHH-10252" )
@RunWith( BytecodeEnhancerRunner.class )
public class CascadeDeleteCollectionWithCollectionInDefaultFetchGroupFalseTest extends BaseCoreFunctionalTestCase {
private Parent originalParent;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[]{Parent.class, Child.class};
}
@Override
protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
@Before
public void prepare() {
// Create a Parent with one Child
originalParent = doInHibernate( this::sessionFactory, s -> {
Parent p = new Parent();
p.setName( "PARENT" );
p.setLazy( "LAZY" );
p.makeChild();
s.persist( p );
return p;
}
);
}
@Test
public void testManagedWithUninitializedAssociation() {
// Delete the Parent
doInHibernate( this::sessionFactory, s -> {
Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" )
.setParameter( "name", "PARENT" )
.uniqueResult();
checkInterceptor( loadedParent, false );
assertFalse( Hibernate.isPropertyInitialized( loadedParent, "children" ) );
s.delete( loadedParent );
} );
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
}
@Test
@TestForIssue(jiraKey = "HHH-13129")
public void testManagedWithInitializedAssociation() {
// Delete the Parent
doInHibernate( this::sessionFactory, s -> {
Parent loadedParent = (Parent) s.createQuery( "SELECT p FROM Parent p WHERE name=:name" )
.setParameter( "name", "PARENT" )
.uniqueResult();
checkInterceptor( loadedParent, false );
loadedParent.getChildren();
assertTrue( Hibernate.isPropertyInitialized( loadedParent, "children" ) );
s.delete( loadedParent );
} );
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
}
@Test
@TestForIssue(jiraKey = "HHH-13129")
public void testDetachedWithUninitializedAssociation() {
final Parent detachedParent = doInHibernate( this::sessionFactory, s -> {
return s.get( Parent.class, originalParent.getId() );
} );
assertFalse( Hibernate.isPropertyInitialized( detachedParent, "children" ) );
checkInterceptor( detachedParent, false );
// Delete the detached Parent with uninitialized children
doInHibernate( this::sessionFactory, s -> {
s.delete( detachedParent );
} );
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
}
@Test
@TestForIssue(jiraKey = "HHH-13129")
public void testDetachedWithInitializedAssociation() {
final Parent detachedParent = doInHibernate( this::sessionFactory, s -> {
Parent parent = s.get( Parent.class, originalParent.getId() );
assertFalse( Hibernate.isPropertyInitialized( parent, "children" ) );
// initialize collection before detaching
parent.getChildren();
return parent;
} );
assertTrue( Hibernate.isPropertyInitialized( detachedParent, "children" ) );
checkInterceptor( detachedParent, false );
// Delete the detached Parent with initialized children
doInHibernate( this::sessionFactory, s -> {
s.delete( detachedParent );
} );
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
}
@Test
@TestForIssue(jiraKey = "HHH-13129")
public void testDetachedOriginal() {
// originalParent#children should be initialized
assertTrue( Hibernate.isPropertyInitialized( originalParent, "children" ) );
checkInterceptor( originalParent, true );
// Delete the Parent
doInHibernate( this::sessionFactory, s -> {
s.delete( originalParent );
} );
// If the lazy relation is not fetch on cascade there is a constraint violation on commit
}
private void checkInterceptor(Parent parent, boolean isNullExpected) {
final BytecodeEnhancementMetadata bytecodeEnhancementMetadata = sessionFactory().getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( Parent.class )
.getBytecodeEnhancementMetadata();
if ( isNullExpected ) {
// if a null Interceptor is expected, then there shouldn't be any uninitialized attributes
assertFalse( bytecodeEnhancementMetadata.hasUnFetchedAttributes( parent ) );
assertNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) );
}
else {
assertNotNull( bytecodeEnhancementMetadata.extractInterceptor( parent ) );
}
}
// --- //
@Entity( name = "Parent" )
@Table( name = "PARENT" )
public static class Parent {
Long id;
String name;
List<Child> children = new ArrayList<>();
String lazy;
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
Long getId() {
return id;
}
void setId(Long id) {
this.id = id;
}
@OneToMany( mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY )
List<Child> getChildren() {
return Collections.unmodifiableList( children );
}
void setChildren(List<Child> children) {
this.children = children;
}
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
@Basic( fetch = FetchType.LAZY )
String getLazy() {
return lazy;
}
void setLazy(String lazy) {
this.lazy = lazy;
}
void makeChild() {
Child c = new Child();
c.setParent( this );
children.add( c );
}
}
@Entity
@Table( name = "CHILD" )
private static class Child {
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
Long id;
@ManyToOne( optional = false )
@JoinColumn( name = "parent_id" )
Parent parent;
Long getId() {
return id;
}
void setId(Long id) {
this.id = id;
}
Parent getParent() {
return parent;
}
void setParent(Parent parent) {
this.parent = parent;
}
}
}

View File

@ -0,0 +1,343 @@
package org.hibernate.orm.test.bytecode.enhancement.cascade;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.Hibernate;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
/**
* Same as {@link CascadeDeleteCollectionTest},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*
* @author Bolek Ziobrowski
* @author Gail Badner
*/
@RunWith(BytecodeEnhancerRunner.class)
@TestForIssue(jiraKey = "HHH-13129")
public class CascadeOnUninitializedWithCollectionInDefaultFetchGroupFalseTest extends BaseNonConfigCoreFunctionalTestCase {
private SQLStatementInterceptor sqlInterceptor;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Person.class, Address.class };
}
@Override
protected void addSettings(Map<String,Object> settings) {
super.addSettings( settings );
settings.put( AvailableSettings.FORMAT_SQL, "true" );
sqlInterceptor = new SQLStatementInterceptor( settings );
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
@Test
public void testMergeDetachedEnhancedEntityWithUninitializedManyToOne() {
final Person person = persistPersonWithManyToOne();
sqlInterceptor.clear();
// get a detached Person
final Person detachedPerson = fromTransaction(
session -> session.get( Person.class, person.getId() )
);
// loading Person should lead to one SQL
assertThat( sqlInterceptor.getQueryCount(), is( 1 ) );
// primaryAddress should be "initialized" as an enhanced-proxy
assertTrue( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) );
assertThat( detachedPerson.getPrimaryAddress(), not( instanceOf( HibernateProxy.class ) ) );
assertFalse( Hibernate.isInitialized( detachedPerson.getPrimaryAddress() ) );
// alter the detached reference
detachedPerson.setName( "newName" );
final Person mergedPerson = fromTransaction(
session -> (Person) session.merge( detachedPerson )
);
// 1) select Person#addresses
// 2) select Person#primaryAddress
// 3) update Person
assertThat( sqlInterceptor.getQueryCount(), is( 3 ) );
// primaryAddress should not be initialized
assertTrue( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) );
assertThat( detachedPerson.getPrimaryAddress(), not( instanceOf( HibernateProxy.class ) ) );
assertFalse( Hibernate.isInitialized( detachedPerson.getPrimaryAddress() ) );
assertEquals( "newName", mergedPerson.getName() );
}
@Test
public void testDeleteEnhancedEntityWithUninitializedManyToOne() {
Person person = persistPersonWithManyToOne();
sqlInterceptor.clear();
// get a detached Person
Person detachedPerson = fromTransaction(
session -> session.get( Person.class, person.getId() )
);
// loading Person should lead to one SQL
assertThat( sqlInterceptor.getQueryCount(), is( 1 ) );
// primaryAddress should be initialized as an enhance-proxy
assertTrue( Hibernate.isPropertyInitialized( detachedPerson, "primaryAddress" ) );
assertThat( detachedPerson, not( instanceOf( HibernateProxy.class ) ) );
assertFalse( Hibernate.isInitialized( detachedPerson.getPrimaryAddress() ) );
sqlInterceptor.clear();
// deleting detachedPerson should result in detachedPerson.primaryAddress being initialized,
// so that the DELETE operation can be cascaded to it.
inTransaction(
session -> session.delete( detachedPerson )
);
// 1) select Person#addresses
// 2) select Person#primaryAddress
// 3) delete Person
// 4) select primary Address
assertThat( sqlInterceptor.getQueryCount(), is( 4 ) );
// both the Person and its Address should be deleted
inTransaction(
session -> {
assertNull( session.get( Person.class, person.getId() ) );
assertNull( session.get( Person.class, person.getPrimaryAddress().getId() ) );
}
);
}
@Test
public void testMergeDetachedEnhancedEntityWithUninitializedOneToMany() {
Person person = persistPersonWithOneToMany();
// get a detached Person
Person detachedPerson = TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
return session.get( Person.class, person.getId() );
}
);
// address should not be initialized
assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) );
detachedPerson.setName( "newName" );
Person mergedPerson = TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
return (Person) session.merge( detachedPerson );
}
);
// address should be initialized
assertTrue( Hibernate.isPropertyInitialized( mergedPerson, "addresses" ) );
assertEquals( "newName", mergedPerson.getName() );
}
@Test
public void testDeleteEnhancedEntityWithUninitializedOneToMany() {
Person person = persistPersonWithOneToMany();
// get a detached Person
Person detachedPerson = TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
return session.get( Person.class, person.getId() );
}
);
// address should not be initialized
assertFalse( Hibernate.isPropertyInitialized( detachedPerson, "addresses" ) );
// deleting detachedPerson should result in detachedPerson.address being initialized,
// so that the DELETE operation can be cascaded to it.
TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
session.delete( detachedPerson );
}
);
// both the Person and its Address should be deleted
TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
assertNull( session.get( Person.class, person.getId() ) );
assertNull( session.get( Person.class, person.getAddresses().iterator().next().getId() ) );
}
);
}
public Person persistPersonWithManyToOne() {
Address address = new Address();
address.setDescription( "ABC" );
final Person person = new Person();
person.setName( "John Doe" );
person.setPrimaryAddress( address );
TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
session.persist( person );
}
);
return person;
}
public Person persistPersonWithOneToMany() {
Address address = new Address();
address.setDescription( "ABC" );
final Person person = new Person();
person.setName( "John Doe" );
person.getAddresses().add( address );
TransactionUtil.doInHibernate(
this::sessionFactory, session -> {
session.persist( person );
}
);
return person;
}
@Entity
@Table(name = "TEST_PERSON")
public static class Person {
@Id
@GeneratedValue
private Long id;
@Column(name = "NAME", length = 300, nullable = true)
private String name;
@ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
@JoinColumn(name = "primary_address_id")
@LazyToOne(LazyToOneOption.NO_PROXY)
private Address primaryAddress;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn( name = "person_id" )
private Set<Address> addresses = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getPrimaryAddress() {
return primaryAddress;
}
public void setPrimaryAddress(Address primaryAddress) {
this.primaryAddress = primaryAddress;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
@Entity
@Table(name = "TEST_ADDRESS")
public static class Address {
@Id
@GeneratedValue
private Long id;
@Column(name = "DESCRIPTION", length = 300, nullable = true)
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.dirty;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
/**
* Same as {@link DirtyTrackingCollectionInDefaultFetchGroupTest},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*
* @author Christian Beikov
*/
@TestForIssue( jiraKey = "HHH-14348" )
@RunWith( BytecodeEnhancerRunner.class )
public class DirtyTrackingCollectionInDefaultFetchGroupFalseTest extends BaseCoreFunctionalTestCase {
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{StringsEntity.class};
}
@Override
protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
@Before
public void prepare() {
doInJPA( this::sessionFactory, em -> {
StringsEntity entity = new StringsEntity();
entity.id = 1L;
entity.someStrings = new ArrayList<>( Arrays.asList( "a", "b", "c" ) );
em.persist( entity );
} );
}
@Test
public void test() {
doInJPA( this::sessionFactory, entityManager -> {
StringsEntity entity = entityManager.find( StringsEntity.class, 1L );
entityManager.flush();
BytecodeLazyAttributeInterceptor interceptor = (BytecodeLazyAttributeInterceptor) ( (PersistentAttributeInterceptable) entity )
.$$_hibernate_getInterceptor();
assertTrue( interceptor.hasAnyUninitializedAttributes() );
assertFalse( interceptor.isAttributeLoaded( "someStrings" ) );
assertFalse( interceptor.isAttributeLoaded( "someStringEntities" ) );
} );
}
// --- //
@Entity
@Table( name = "STRINGS_ENTITY" )
private static class StringsEntity {
@Id
Long id;
@ElementCollection
List<String> someStrings;
@ManyToOne(fetch = FetchType.LAZY)
StringsEntity parent;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
Set<StringsEntity> someStringEntities;
}
}

View File

@ -0,0 +1,171 @@
/*
* 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;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.Hibernate.isPropertyInitialized;
import static org.hibernate.testing.bytecode.enhancement.EnhancerTestUtils.checkDirtyTracking;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.orm.test.bytecode.enhancement.dirty.DirtyTrackingCollectionInDefaultFetchGroupTest;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
/**
* Same as {@link LazyCollectionDetachTest},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*/
@TestForIssue(jiraKey = "HHH-12260")
@RunWith(BytecodeEnhancerRunner.class)
public class LazyCollectionDetachWithCollectionInDefaultFetchGroupFalseTest extends BaseCoreFunctionalTestCase {
private static final int CHILDREN_SIZE = 10;
private Long parentID;
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ Parent.class, Child.class };
}
@Override
protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
@Before
public void prepare() {
doInHibernate( this::sessionFactory, s -> {
Parent parent = new Parent();
parent.setChildren( new ArrayList<>() );
for ( int i = 0; i < CHILDREN_SIZE; i++ ) {
Child child = new Child();
child.parent = parent;
s.persist( child );
}
s.persist( parent );
parentID = parent.id;
} );
}
@Test
public void testDetach() {
doInHibernate( this::sessionFactory, s -> {
Parent parent = s.find( Parent.class, parentID );
assertThat( parent, notNullValue() );
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
assertFalse( isPropertyInitialized( parent, "children" ) );
checkDirtyTracking( parent );
s.detach( parent );
s.flush();
} );
}
@Test
public void testDetachProxy() {
doInHibernate( this::sessionFactory, s -> {
Parent parent = s.getReference( Parent.class, parentID );
checkDirtyTracking( parent );
s.detach( parent );
s.flush();
} );
}
@Test
public void testRefresh() {
doInHibernate( this::sessionFactory, s -> {
Parent parent = s.find( Parent.class, parentID );
assertThat( parent, notNullValue() );
assertThat( parent, not( instanceOf( HibernateProxy.class ) ) );
assertFalse( isPropertyInitialized( parent, "children" ) );
checkDirtyTracking( parent );
s.refresh( parent );
s.flush();
} );
}
@Entity(name = "Parent")
@Table(name = "PARENT")
private static class Parent {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
List<Child> children;
void setChildren(List<Child> children) {
this.children = children;
}
}
@Entity(name = "Child")
@Table(name = "CHILD")
private static class Child {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Parent parent;
Child() {
}
}
}

View File

@ -0,0 +1,350 @@
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.core.Is.is;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.Statistics;
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.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import org.hamcrest.MatcherAssert;
/**
* Same as {@link SimpleUpdateTestWithLazyLoading},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*
* @author Andrea Boriero
*/
@TestForIssue(jiraKey = "HHH-11147")
@RunWith(BytecodeEnhancerRunner.class)
@EnhancementOptions(lazyLoading = true)
public class SimpleUpdateWithLazyLoadingWithCollectionInDefaultFetchGroupFalseTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" );
ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" );
ssrb.applySetting( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" );
ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" );
ssrb.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
private static final int CHILDREN_SIZE = 10;
private Long lastChildID;
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Parent.class, Child.class, Person.class };
}
@Before
public void prepare() {
doInHibernate( this::sessionFactory, s -> {
Parent parent = new Parent();
for ( int i = 0; i < CHILDREN_SIZE; i++ ) {
Child child = new Child();
// Association management should kick in here
child.parent = parent;
Person relative = new Person();
relative.setName( "Luigi" );
child.addRelative( relative );
s.persist( relative );
s.persist( child );
lastChildID = child.id;
}
s.persist( parent );
} );
}
@After
public void tearDown() {
doInHibernate( this::sessionFactory, s -> {
s.createQuery( "delete from Child" ).executeUpdate();
s.createQuery( "delete from Parent" ).executeUpdate();
} );
}
@Test
public void updateSimpleField() {
final Statistics stats = sessionFactory().getStatistics();
stats.clear();
final String updatedName = "Barrabas_";
final EntityPersister childPersister = sessionFactory().getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( Child.class.getName() );
final int relativesAttributeIndex = childPersister.getEntityMetamodel().getPropertyIndex( "relatives" );
inTransaction(
session -> {
stats.clear();
Child loadedChild = session.load( Child.class, lastChildID );
final PersistentAttributeInterceptable interceptable = (PersistentAttributeInterceptable) loadedChild;
final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
MatcherAssert.assertThat( interceptor, instanceOf( EnhancementAsProxyLazinessInterceptor.class ) );
final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
loadedChild.setName( updatedName );
// ^ should have triggered "base fetch group" initialization which would mean a SQL select
assertEquals( 1, stats.getPrepareStatementCount() );
// check that the `#setName` "persisted"
assertThat( loadedChild.getName(), is( updatedName ) );
assertEquals( 1, stats.getPrepareStatementCount() );
final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild );
assertThat(
entry.getLoadedState()[ relativesAttributeIndex ],
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
);
// force a flush - the relatives collection should still be UNFETCHED_PROPERTY afterwards
session.flush();
final EntityEntry updatedEntry = session.getPersistenceContext().getEntry( loadedChild );
assertThat( updatedEntry, sameInstance( entry ) );
assertThat(
entry.getLoadedState()[ relativesAttributeIndex ],
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
);
session.getEventListenerManager();
}
);
inTransaction(
session -> {
Child loadedChild = session.load( Child.class, lastChildID );
assertThat( loadedChild.getName(), is( updatedName ) );
final EntityEntry entry = session.getPersistenceContext().getEntry( loadedChild );
assertThat(
entry.getLoadedState()[ relativesAttributeIndex ],
is( LazyPropertyInitializer.UNFETCHED_PROPERTY )
);
}
);
}
@Test
public void testUpdateAssociation() {
String updatedName = "Barrabas_";
String parentName = "Yodit";
doInHibernate( this::sessionFactory, s -> {
final Statistics stats = sessionFactory().getStatistics();
stats.clear();
Child loadedChild = s.load( Child.class, lastChildID );
loadedChild.setName( updatedName );
Parent parent = new Parent();
parent.setName( parentName );
assertEquals( 1, stats.getPrepareStatementCount() );
loadedChild.setParent( parent );
assertEquals( 1, stats.getPrepareStatementCount() );
assertThat( loadedChild.getParent().getName(), is( parentName ) );
assertEquals( 1, stats.getPrepareStatementCount() );
s.save( parent );
} );
doInHibernate( this::sessionFactory, s -> {
Child loadedChild = s.load( Child.class, lastChildID );
assertThat( Hibernate.isInitialized( loadedChild ), is( false ) );
assertThat( loadedChild.getName(), is( updatedName ) );
assertThat( Hibernate.isInitialized( loadedChild ), is( true ) );
assertThat( Hibernate.isInitialized( loadedChild.getParent() ), is( false ) );
assertThat( loadedChild.getParent().getName(), is( parentName ) );
assertThat( Hibernate.isInitialized( loadedChild.getParent() ), is( true ) );
} );
}
@Test
public void testUpdateCollection() {
doInHibernate( this::sessionFactory, s -> {
final Statistics stats = sessionFactory().getStatistics();
stats.clear();
Child loadedChild = s.load( Child.class, lastChildID );
assertEquals( 0, stats.getPrepareStatementCount() );
Person relative = new Person();
relative.setName( "Luis" );
assertThat( Hibernate.isInitialized( loadedChild ), is( false ) );
loadedChild.addRelative( relative );
assertThat( Hibernate.isInitialized( loadedChild ), is( true ) );
assertEquals( 2, stats.getPrepareStatementCount() );
s.persist( relative );
} );
doInHibernate( this::sessionFactory, s -> {
Child loadedChild = s.load( Child.class, lastChildID );
assertThat( loadedChild.getRelatives().size(), is( 2 ) );
} );
}
@Entity(name = "Parent")
@Table(name = "PARENT")
private static class Parent {
String name;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<Child> children;
void setChildren(List<Child> children) {
this.children = children;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity(name = "Person")
@Table(name = "Person")
private static class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity(name = "Child")
@Table(name = "CHILD")
private static class Child {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Parent parent;
@OneToMany
List<Person> relatives;
String name;
Child() {
}
Child(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public List<Person> getRelatives() {
return relatives;
}
public void setRelatives(List<Person> relatives) {
this.relatives = relatives;
}
public void addRelative(Person person) {
if ( this.relatives == null ) {
this.relatives = new ArrayList<>();
}
this.relatives.add( person );
}
}
}

View File

@ -0,0 +1,324 @@
/*
* 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.ondemandload;
import static org.hibernate.Hibernate.isPropertyInitialized;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.boot.internal.SessionFactoryBuilderImpl;
import org.hibernate.boot.internal.SessionFactoryOptionsBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderService;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.SimpleUpdateTestWithLazyLoading;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
/**
* Same as {@link OnDemandLoadTest},
* but with {@code collectionInDefaultFetchGroup} set to {@code false} explicitly.
* <p>
* Kept here for <a href="https://github.com/hibernate/hibernate-orm/pull/5252#pullrequestreview-1095843220">historical reasons</a>.
*
* @author Luis Barreiro
*/
@TestForIssue( jiraKey = "HHH-10055" )
@RunWith( BytecodeEnhancerRunner.class )
public class OnDemandLoadWithCollectionInDefaultFetchGroupFalseTest extends BaseCoreFunctionalTestCase {
@Override
public Class[] getAnnotatedClasses() {
return new Class[]{Store.class, Inventory.class, Product.class};
}
@Override
protected void configure(Configuration configuration) {
configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" );
configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" );
configuration.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" );
}
@Override
protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
SessionFactoryBuilderService.class,
(SessionFactoryBuilderService) (metadata, bootstrapContext) -> {
SessionFactoryOptionsBuilder optionsBuilder = new SessionFactoryOptionsBuilder(
metadata.getMetadataBuildingOptions().getServiceRegistry(),
bootstrapContext
);
// We want to test with this setting set to false explicitly,
// because another test already takes care of the default.
optionsBuilder.enableCollectionInDefaultFetchGroup( false );
return new SessionFactoryBuilderImpl( metadata, optionsBuilder );
}
);
}
@Before
public void prepare() {
doInHibernate( this::sessionFactory, s -> {
Store store = new Store( 1L ).setName( "Acme Super Outlet" );
s.persist( store );
Product product = new Product( "007" ).setName( "widget" ).setDescription( "FooBar" );
s.persist( product );
store.addInventoryProduct( product ).setQuantity( 10L ).setStorePrice( new BigDecimal( 500 ) );
} );
}
@Test
public void testClosedSession() {
sessionFactory().getStatistics().clear();
Store[] store = new Store[1];
doInHibernate( this::sessionFactory, s -> {
// first load the store, making sure it is not initialized
store[0] = s.load( Store.class, 1L );
assertNotNull( store[0] );
assertFalse( isPropertyInitialized( store[0], "inventories" ) );
assertEquals( 1, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 0, sessionFactory().getStatistics().getSessionCloseCount() );
} );
assertEquals( 1, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 1, sessionFactory().getStatistics().getSessionCloseCount() );
store[0].getInventories();
assertTrue( isPropertyInitialized( store[0], "inventories" ) );
assertEquals( 2, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
}
@Test
public void testClearedSession() {
sessionFactory().getStatistics().clear();
doInHibernate( this::sessionFactory, s -> {
// first load the store, making sure collection is not initialized
Store store = s.get( Store.class, 1L );
assertNotNull( store );
assertFalse( isPropertyInitialized( store, "inventories" ) );
assertEquals( 1, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 0, sessionFactory().getStatistics().getSessionCloseCount() );
// then clear session and try to initialize collection
s.clear();
assertNotNull( store );
assertFalse( isPropertyInitialized( store, "inventories" ) );
store.getInventories().size();
assertTrue( isPropertyInitialized( store, "inventories" ) );
// the extra Sessions are the temp Sessions needed to perform the init:
// first the entity, then the collection (since it's lazy)
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
// clear Session again. The collection should still be recognized as initialized from above
s.clear();
assertNotNull( store );
assertTrue( isPropertyInitialized( store, "inventories" ) );
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
// lets clear the Session again and this time reload the Store
s.clear();
store = s.get( Store.class, 1L );
s.clear();
assertNotNull( store );
// collection should be back to uninitialized since we have a new entity instance
assertFalse( isPropertyInitialized( store, "inventories" ) );
assertEquals( 3, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 2, sessionFactory().getStatistics().getSessionCloseCount() );
store.getInventories().size();
assertTrue( isPropertyInitialized( store, "inventories" ) );
// the extra Sessions are the temp Sessions needed to perform the init:
// first the entity, then the collection (since it's lazy)
assertEquals( 5, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 4, sessionFactory().getStatistics().getSessionCloseCount() );
// clear Session again. The collection should still be recognized as initialized from above
s.clear();
assertNotNull( store );
assertTrue( isPropertyInitialized( store, "inventories" ) );
assertEquals( 5, sessionFactory().getStatistics().getSessionOpenCount() );
assertEquals( 4, sessionFactory().getStatistics().getSessionCloseCount() );
} );
}
@After
public void cleanup() throws Exception {
doInHibernate( this::sessionFactory, s -> {
Store store = s.find( Store.class, 1L );
s.delete( store );
Product product= s.find( Product.class, "007" );
s.delete( product );
} );
}
// --- //
@Entity
@Table( name = "STORE" )
private static class Store {
@Id
Long id;
String name;
@OneToMany( mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
List<Inventory> inventories = new ArrayList<>();
@Version
Integer version;
Store() {
}
Store(long id) {
this.id = id;
}
Store setName(String name) {
this.name = name;
return this;
}
Inventory addInventoryProduct(Product product) {
Inventory inventory = new Inventory( this, product );
inventories.add( inventory );
return inventory;
}
public List<Inventory> getInventories() {
return Collections.unmodifiableList( inventories );
}
}
@Entity
@Table( name = "INVENTORY" )
private static class Inventory {
@Id
@GeneratedValue
@GenericGenerator( name = "increment", strategy = "increment" )
Long id = -1L;
@ManyToOne
@JoinColumn( name = "STORE_ID" )
Store store;
@ManyToOne
@JoinColumn( name = "PRODUCT_ID" )
Product product;
Long quantity;
BigDecimal storePrice;
public Inventory() {
}
public Inventory(Store store, Product product) {
this.store = store;
this.product = product;
}
Inventory setStore(Store store) {
this.store = store;
return this;
}
Inventory setProduct(Product product) {
this.product = product;
return this;
}
Inventory setQuantity(Long quantity) {
this.quantity = quantity;
return this;
}
Inventory setStorePrice(BigDecimal storePrice) {
this.storePrice = storePrice;
return this;
}
}
@Entity
@Table( name = "PRODUCT" )
private static class Product {
@Id
String id;
String name;
String description;
BigDecimal msrp;
@Version
Long version;
Product() {
}
Product(String id) {
this.id = id;
}
Product setName(String name) {
this.name = name;
return this;
}
Product setDescription(String description) {
this.description = description;
return this;
}
Product setMsrp(BigDecimal msrp) {
this.msrp = msrp;
return this;
}
}
}