HHH-15473 Copy/paste a few select tests to set collectionInDefaultFetchGroup to false explicitly
This commit is contained in:
parent
16f865f100
commit
560722dfaa
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue