Skip to content

Commit c35287c

Browse files
marko-bekhtayrodiere
authored andcommitted
HHH-19865 Change conditions under which pending bulk operation cleanup actions are executed
1 parent d364f68 commit c35287c

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ public void closeWithoutOpenChecks() {
403403
}
404404
}
405405
finally {
406-
if ( actionQueue.hasAfterTransactionActions() ) {
406+
// E.g. when we are in the JTA context the session can get closed while the transaction is still active
407+
// and JTA will call the AfterCompletion itself. Hence, we don't want to clear out the action queue callbacks at this point:
408+
if ( !getTransactionCoordinator().isTransactionActive() && actionQueue.hasAfterTransactionActions() ) {
407409
SESSION_LOGGER.warn( "Closing session with unprocessed clean up bulk operations, forcing their execution" );
408410
actionQueue.executePendingBulkOperationCleanUpActions();
409411
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.actionqueue;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.GeneratedValue;
9+
import jakarta.persistence.Id;
10+
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
11+
import org.hibernate.cfg.AvailableSettings;
12+
import org.hibernate.engine.spi.SessionFactoryImplementor;
13+
import org.hibernate.engine.spi.SessionImplementor;
14+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
15+
import org.hibernate.orm.test.jpa.transaction.JtaPlatformSettingProvider;
16+
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
17+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
18+
import org.hibernate.testing.orm.junit.Jpa;
19+
import org.hibernate.testing.orm.junit.SessionFactory;
20+
import org.hibernate.testing.orm.junit.Setting;
21+
import org.hibernate.testing.orm.junit.SettingProvider;
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.Test;
24+
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
import static org.assertj.core.api.Assertions.fail;
28+
import static org.junit.jupiter.api.Assertions.assertEquals;
29+
import static org.junit.jupiter.api.Assertions.assertFalse;
30+
import static org.junit.jupiter.api.Assertions.assertTrue;
31+
32+
@Jpa(
33+
annotatedClasses = JtaCustomAfterCompletionTest.SimpleEntity.class,
34+
settingProviders = @SettingProvider(settingName = AvailableSettings.JTA_PLATFORM,
35+
provider = JtaPlatformSettingProvider.class),
36+
integrationSettings = {
37+
@Setting(name = AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, value = "jta"),
38+
@Setting(name = AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS, value = "true"),
39+
@Setting(name = AvailableSettings.JPA_TRANSACTION_COMPLIANCE, value = "true"),
40+
}
41+
)
42+
@SessionFactory
43+
public class JtaCustomAfterCompletionTest {
44+
45+
@AfterEach
46+
public void afterEach(EntityManagerFactoryScope scope) {
47+
scope.getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ).getSchemaManager()
48+
.truncateMappedObjects();
49+
}
50+
51+
@Test
52+
public void success(EntityManagerFactoryScope scope) {
53+
AtomicBoolean called = new AtomicBoolean( false );
54+
try {
55+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
56+
scope.inEntityManager( session -> {
57+
session.unwrap( SessionImplementor.class ).getActionQueue()
58+
.registerCallback( new AfterTransactionCompletionProcess() {
59+
@Override
60+
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
61+
called.set( true );
62+
}
63+
} );
64+
assertFalse( called.get() );
65+
66+
session.persist( new SimpleEntity( "jack" ) );
67+
68+
} );
69+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
70+
71+
assertTrue( called.get() );
72+
73+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
74+
// Check that the transaction was committed
75+
scope.inEntityManager( session -> {
76+
long count = session.createQuery( "select count(*) from SimpleEntity", Long.class )
77+
.getSingleResult();
78+
assertEquals( 1L, count );
79+
} );
80+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
81+
}
82+
catch (Exception e) {
83+
// TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getTransaction().rollback();
84+
fail( "Should not have thrown an exception" );
85+
}
86+
}
87+
88+
@Test
89+
public void rollback(EntityManagerFactoryScope scope) {
90+
try {
91+
AtomicBoolean called = new AtomicBoolean( false );
92+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
93+
scope.inEntityManager( session -> {
94+
session.unwrap( SessionImplementor.class ).getActionQueue()
95+
.registerCallback( new AfterTransactionCompletionProcess() {
96+
@Override
97+
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
98+
called.set( true );
99+
}
100+
} );
101+
assertFalse( called.get() );
102+
scope.inEntityManager( theSession -> {
103+
theSession.persist( new SimpleEntity( "jack" ) );
104+
theSession.getTransaction().setRollbackOnly();
105+
} );
106+
} );
107+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().rollback();
108+
assertTrue( called.get() );
109+
110+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
111+
// Check that the transaction was not committed
112+
scope.inEntityManager( session -> {
113+
long count = session.createQuery( "select count(*) from SimpleEntity", Long.class )
114+
.getSingleResult();
115+
assertEquals( 0L, count );
116+
} );
117+
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
118+
}
119+
catch (Exception e) {
120+
// TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getTransaction().rollback();
121+
fail( "Should not have thrown an exception", e );
122+
}
123+
}
124+
125+
@Entity(name = "SimpleEntity")
126+
public static class SimpleEntity {
127+
@Id
128+
@GeneratedValue
129+
private Integer id;
130+
131+
private String name;
132+
133+
SimpleEntity() {
134+
}
135+
136+
SimpleEntity(String name) {
137+
this.name = name;
138+
}
139+
140+
public Integer getId() {
141+
return id;
142+
}
143+
144+
public void setId(Integer id) {
145+
this.id = id;
146+
}
147+
148+
public String getName() {
149+
return name;
150+
}
151+
152+
public void setName(String name) {
153+
this.name = name;
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)