From 08ffae74eb79182ec590c34af66b785d5ba9c16e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 24 Jan 2024 11:18:03 +0100 Subject: [PATCH] HHH-17668 Add test for issue --- .../refresh/MergeAndRefreshTest.java | 118 +++++++++ ...LockRefreshReferencedAndCascadingTest.java | 242 ++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/MergeAndRefreshTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/MergeAndRefreshTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/MergeAndRefreshTest.java new file mode 100644 index 0000000000..32b5a3692c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/refresh/MergeAndRefreshTest.java @@ -0,0 +1,118 @@ +package org.hibernate.orm.test.bytecode.enhancement.refresh; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Test; +import org.junit.runner.RunWith; + +import jakarta.persistence.Cacheable; +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.OneToOne; + +@RunWith(BytecodeEnhancerRunner.class) +@JiraKey("HHH-17668") +public class MergeAndRefreshTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Phase.class, + PhaseDescription.class + }; + } + + @Test + public void testRefresh() { + Long phaseId = 1L; + inTransaction( + session -> { + PhaseDescription description = new PhaseDescription("phase 1"); + Phase phase = new Phase( phaseId, description ); + session.persist( phase ); + } + ); + + Phase phase = fromTransaction( + session -> { + return session.find( Phase.class, phaseId ); + } + ); + + inTransaction( + session -> { + Phase merged = session.merge( phase ); + session.refresh( merged ); + } + ); + } + + @Entity(name = "Phase") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Phase { + @Id + private Long id; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "phase_description_id") + private PhaseDescription description; + + private String name; + + public Phase() { + } + + public Phase(Long id, PhaseDescription description) { + this.id = id; + this.description = description; + this.description.phase = this; + } + + public Long getId() { + return id; + } + + public PhaseDescription getDescription() { + return description; + } + } + + @Entity(name = "PhaseDescription") + public static class PhaseDescription { + @Id + @GeneratedValue + private Long id; + + private String name; + + public PhaseDescription() { + } + + public PhaseDescription(String name) { + this.name = name; + } + + @OneToOne(mappedBy = "description") + @Fetch(value = FetchMode.SELECT) + private Phase phase; + + public Long getId() { + return id; + } + + public Phase getPhase() { + return phase; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java new file mode 100644 index 0000000000..2dd59f56a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java @@ -0,0 +1,242 @@ +package org.hibernate.orm.test.locking; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.LockModeType; +import jakarta.persistence.OneToOne; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Jpa( + annotatedClasses = { + LockRefreshReferencedAndCascadingTest.MainEntity.class, + LockRefreshReferencedAndCascadingTest.ReferencedEntity.class, + LockRefreshReferencedAndCascadingTest.AnotherReferencedEntity.class, + } +) +public class LockRefreshReferencedAndCascadingTest { + + @BeforeAll + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final AnotherReferencedEntity anotherReferencedEntity = new AnotherReferencedEntity( + 1L, + "another lazy" + ); + final ReferencedEntity e1 = new ReferencedEntity( 0L, "lazy", anotherReferencedEntity ); + final ReferencedEntity e2 = new ReferencedEntity( 1L, "eager", null ); + entityManager.persist( e1 ); + entityManager.persist( e2 ); + final MainEntity e3 = new MainEntity( 0L, e1, e2 ); + entityManager.persist( e3 ); + } + ); + } + + @Test + public void testRefreshBeforeRead(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + MainEntity m = entityManager.find( MainEntity.class, 0L ); + assertNotNull( m ); + ReferencedEntity lazyReference = m.referencedLazy(); + ReferencedEntity eagerReference = m.referencedEager(); + assertNotNull( lazyReference ); + assertNotNull( eagerReference ); + assertFalse( Hibernate.isInitialized( lazyReference ) ); + + // First refresh, then access + entityManager.refresh( eagerReference, LockModeType.PESSIMISTIC_WRITE ); + assertFalse( Hibernate.isInitialized( lazyReference ) ); + + entityManager.refresh( lazyReference, LockModeType.PESSIMISTIC_WRITE ); + assertTrue( Hibernate.isInitialized( lazyReference ) ); + assertTrue( Hibernate.isInitialized( lazyReference.anotherReferencedEntity ) ); + + assertEquals( "lazy", lazyReference.status() ); + assertEquals( "eager", eagerReference.status() ); + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( lazyReference ) ); + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( lazyReference.getAnotherReferencedEntity() ) ); + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( eagerReference ) ); + } ); + } + + @Test + public void testRefresh(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + MainEntity m = entityManager.find( MainEntity.class, 0L ); + assertNotNull( m ); + ReferencedEntity lazyReference = m.referencedLazy(); + ReferencedEntity eagerReference = m.referencedEager(); + assertNotNull( lazyReference ); + assertNotNull( eagerReference ); + assertFalse( Hibernate.isInitialized( lazyReference ) ); + + entityManager.refresh( m ); + // CascadeType.REFRESH will trigger the initialization + assertTrue( Hibernate.isInitialized( lazyReference ) ); + + } ); + } + + @Test + public void testRefreshAfterRead(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + MainEntity m = entityManager.find( MainEntity.class, 0L ); + assertNotNull( m ); + ReferencedEntity lazyReference = m.referencedLazy(); + ReferencedEntity eagerReference = m.referencedEager(); + assertNotNull( lazyReference ); + assertNotNull( eagerReference ); + assertFalse( Hibernate.isInitialized( lazyReference ) ); + + // First access, the refresh + assertEquals( "lazy", lazyReference.status() ); + assertEquals( "eager", eagerReference.status() ); + + entityManager.refresh( lazyReference, LockModeType.PESSIMISTIC_WRITE ); + entityManager.refresh( eagerReference, LockModeType.PESSIMISTIC_WRITE ); + + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( lazyReference ) ); + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( eagerReference ) ); + } ); + } + + @Test + public void testRefreshLockMode(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + MainEntity m = entityManager.find( MainEntity.class, 0L ); + assertNotNull( m ); + ReferencedEntity lazyReference = m.referencedLazy(); + ReferencedEntity eagerReference = m.referencedEager(); + assertNotNull( lazyReference ); + assertNotNull( eagerReference ); + assertFalse( Hibernate.isInitialized( lazyReference ) ); + + entityManager.refresh( m, LockModeType.PESSIMISTIC_WRITE ); + + assertTrue( Hibernate.isInitialized( lazyReference ) ); + AnotherReferencedEntity anotherReferencedEntity = lazyReference.getAnotherReferencedEntity(); + assertTrue( Hibernate.isInitialized( anotherReferencedEntity ) ); + + assertEquals( LockModeType.PESSIMISTIC_WRITE, entityManager.getLockMode( lazyReference ) ); + assertEquals( + LockModeType.PESSIMISTIC_WRITE, + entityManager.getLockMode( anotherReferencedEntity ) + ); + } ); + } + + @Test + public void testFindWithLockMode(EntityManagerFactoryScope scope) { + scope.inTransaction( + session -> { + MainEntity mainEntity = session.find( MainEntity.class, 0L, LockModeType.PESSIMISTIC_WRITE ); + assertThat( session.getLockMode( mainEntity.referencedEager() ) ).isEqualTo( LockModeType.PESSIMISTIC_WRITE ); + } + ); + } + + @Entity(name = "MainEntity") + public static class MainEntity { + @Id + private Long id; + + private String name; + + @OneToOne(cascade = { CascadeType.REFRESH }, fetch = FetchType.LAZY) + @JoinColumn(name = "LAZY_COLUMN") + private ReferencedEntity referencedLazy; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "EAGER_COLUMN") + private ReferencedEntity referencedEager; + + protected MainEntity() { + } + + public MainEntity(Long id, ReferencedEntity lazy, ReferencedEntity eager) { + this.id = id; + this.referencedLazy = lazy; + this.referencedEager = eager; + } + + public ReferencedEntity referencedLazy() { + return referencedLazy; + } + + public ReferencedEntity referencedEager() { + return referencedEager; + } + } + + @Entity(name = "ReferencedEntity") + public static class ReferencedEntity { + + @Id + private Long id; + + private String status; + + @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH }, fetch = FetchType.LAZY) + private AnotherReferencedEntity anotherReferencedEntity; + + protected ReferencedEntity() { + } + + public ReferencedEntity(Long id, String status, AnotherReferencedEntity anotherReferencedEntity) { + this.id = id; + this.status = status; + this.anotherReferencedEntity = anotherReferencedEntity; + } + + public String status() { + return status; + } + + public AnotherReferencedEntity getAnotherReferencedEntity() { + return anotherReferencedEntity; + } + } + + @Entity(name = "AnotherReferencedEntity") + public static class AnotherReferencedEntity { + + @Id + private Long id; + + private String status; + + protected AnotherReferencedEntity() { + } + + public AnotherReferencedEntity(Long id, String status) { + this.id = id; + this.status = status; + } + + public String status() { + return status; + } + } + +}