From 7620e0e3959714dad4e9534051a2db0cdd3b4085 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 12 Apr 2021 17:14:57 +0200 Subject: [PATCH] HHH-14549 Add test for issue --- .../LoadUninitializedCollectionTest.java | 232 ++++++++++++++++++ ...QueryExecutionWithFlushModeAlwaysTest.java | 188 ++++++++++++++ 2 files changed, 420 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadUninitializedCollectionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadingLazyCollectionAfterQueryExecutionWithFlushModeAlwaysTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadUninitializedCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadUninitializedCollectionTest.java new file mode 100644 index 0000000000..86f0ee7727 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadUninitializedCollectionTest.java @@ -0,0 +1,232 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Query; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-14549") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ DirtyCheckEnhancementContext.class, NoDirtyCheckEnhancementContext.class }) +public class LoadUninitializedCollectionTest extends BaseEntityManagerFunctionalTestCase { + + boolean skipTest; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Bank.class, + BankAccount.class, + BankDepartment.class + }; + } + + @Override + protected void addMappings(Map settings) { + String byteCodeProvider = Environment.getProperties().getProperty( AvailableSettings.BYTECODE_PROVIDER ); + if ( byteCodeProvider != null && !Environment.BYTECODE_PROVIDER_NAME_BYTEBUDDY.equals( byteCodeProvider ) ) { + // skip the test if the bytecode provider is Javassist + skipTest = true; + } + } + + + @Before + public void setUp() { + if ( skipTest ) { + return; + } + doInJPA( + this::entityManagerFactory, entityManager -> { + Bank bank = new Bank( 1L, "International" ); + BankAccount bankAccount = new BankAccount( 1L, bank, "1234567890" ); + BankDepartment bankDepartmentA = new BankDepartment( 1L, "A" ); + BankDepartment bankDepartmentB = new BankDepartment( 2L, "B" ); + BankDepartment bankDepartmentC = new BankDepartment( 3L, "C" ); + + bank.addDepartment( bankDepartmentA ); + bank.addDepartment( bankDepartmentB ); + bank.addDepartment( bankDepartmentC ); + + entityManager.persist( bank ); + entityManager.persist( bankAccount ); + entityManager.persist( bankDepartmentA ); + entityManager.persist( bankDepartmentB ); + entityManager.persist( bankDepartmentC ); + } + ); + } + + @Test + public void testLoadAfterNativeQueryExecution() { + if ( skipTest ) { + return; + } + doInJPA( this::entityManagerFactory, entityManager -> { + BankAccount account = entityManager.find( BankAccount.class, 1L ); + + Query nativeQuery = entityManager.createNativeQuery( "SELECT ID FROM BANK" ); + nativeQuery.getResultList(); + + Bank bank = account.getBank(); + List deps = bank.getDepartments(); + + assertEquals( deps.size(), 3 ); + } + ); + } + + @Test + public void testLoadAfterFlush() { + if ( skipTest ) { + return; + } + doInJPA( this::entityManagerFactory, entityManager -> { + BankAccount account = entityManager.find( BankAccount.class, 1L ); + + entityManager.flush(); + + Bank bank = account.getBank(); + List deps = bank.getDepartments(); + + assertEquals( deps.size(), 3 ); + } + ); + } + + @After + public void tearDown() { + if ( skipTest ) { + return; + } + doInJPA( this::entityManagerFactory, entityManager -> { + Bank bank = entityManager.find( Bank.class, 1L ); + bank.getDepartments().forEach( + department -> entityManager.remove( department ) + ); + List accounts = entityManager.createQuery( "from BankAccount" ).getResultList(); + + accounts.forEach( + account -> entityManager.remove( account ) + ); + + entityManager.remove( bank ); + } + ); + } + + + @Entity(name = "Bank") + @Table(name = "BANK") + public static class Bank { + + @Id + @Column(name = "ID") + private Long id; + + private String name; + + public Bank() { + } + + public Bank(Long id, String name) { + this.id = id; + this.name = name; + } + + @OneToMany + private List departments = new ArrayList<>(); + + public List getDepartments() { + return departments; + } + + public void addDepartment(BankDepartment department) { + this.departments.add( department ); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "BankAccount") + public static class BankAccount { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Bank bank; + + private String serialNumber; + + public BankAccount() { + } + + public BankAccount(Long id, Bank bank, String serialNumber) { + this.id = id; + this.bank = bank; + this.serialNumber = serialNumber; + } + + public Bank getBank() { + return bank; + } + + public String getSerialNumber() { + return serialNumber; + } + } + + @Entity(name = "BankDepartment") + public static class BankDepartment { + + @Id + private Long id; + + private String name; + + public BankDepartment() { + } + + public BankDepartment(Long id, String name) { + this.id = id; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadingLazyCollectionAfterQueryExecutionWithFlushModeAlwaysTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadingLazyCollectionAfterQueryExecutionWithFlushModeAlwaysTest.java new file mode 100644 index 0000000000..123f672a31 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/inlinedirtychecking/LoadingLazyCollectionAfterQueryExecutionWithFlushModeAlwaysTest.java @@ -0,0 +1,188 @@ +package org.hibernate.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +import org.hibernate.FlushMode; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue( jiraKey = "HHH-14549") +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ DirtyCheckEnhancementContext.class, NoDirtyCheckEnhancementContext.class }) +public class LoadingLazyCollectionAfterQueryExecutionWithFlushModeAlwaysTest + extends BaseEntityManagerFunctionalTestCase { + + boolean skipTest; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + ProductOrder.class, + Product.class, + City.class + }; + } + + @Override + protected void addMappings(Map settings) { + String byteCodeProvider = Environment.getProperties().getProperty( AvailableSettings.BYTECODE_PROVIDER ); + settings.put( AvailableSettings.FLUSH_MODE, FlushMode.ALWAYS ); + if ( byteCodeProvider != null && !Environment.BYTECODE_PROVIDER_NAME_BYTEBUDDY.equals( byteCodeProvider ) ) { + // skip the test if the bytecode provider is Javassist + skipTest = true; + } + } + + @Before + public void setUp() { + if ( skipTest ) { + return; + } + doInJPA( + this::entityManagerFactory, entityManager -> { + ProductOrder order = new ProductOrder(); + order.setOrderNumber( "12345" ); + + Customer customer = new Customer(); + customer.setProductOrder( order ); + customer.setName( "Fab" ); + + Product product = new Product(); + product.setName( "gloves" ); + + order.addProduct( product ); + + entityManager.persist( order ); + entityManager.persist( product ); + entityManager.persist( customer ); + } + ); + } + + @Test + public void reproducer_Case1() { + if ( skipTest ) { + return; + } + doInJPA( + this::entityManagerFactory, entityManager -> { + List customers = entityManager.createQuery( "select c from Customer c" ).getResultList(); + assertEquals( 1, customers.size() ); + + Customer customer = customers.get( 0 ); + ProductOrder order = customer.getProductOrder(); + assertNotNull( order ); + + entityManager.createQuery( "select c from City c" ).getResultList(); + + assertEquals( 1, order.getProducts().size() ); + } ); + } + + @MappedSuperclass + public static abstract class Base { + @Id + @GeneratedValue + public Long id; + + public Long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + } + + @Entity(name = "Customer") + public static class Customer extends Base { + + private String name; + + @OneToOne(fetch = FetchType.LAZY) + ProductOrder order; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ProductOrder getProductOrder() { + return order; + } + + public void setProductOrder(ProductOrder order) { + this.order = order; + } + } + + @Entity(name = "ProductOrder") + public static class ProductOrder extends Base { + + private String orderNumber; + + @OneToMany + private List products = new ArrayList<>(); + + public String getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + + public List getProducts() { + return products; + } + + public void addProduct(Product product) { + products.add( product ); + } + + } + + @Entity(name = "Product") + public static class Product extends Base { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "City") + public static class City extends Base { + private String name; + } +}