Skip to content

Commit a372c3a

Browse files
committed
HHH-19774 - Automatic flushing for child session with shared connection/tx
HHH-19808 - Automatic closing for child session with shared connection/tx
1 parent 90c6d27 commit a372c3a

File tree

14 files changed

+389
-64
lines changed

14 files changed

+389
-64
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.engine.creation.internal;
6+
7+
/**
8+
* Callbacks from a parent session to a child session for certain events.
9+
*
10+
* @author Steve Ebersole
11+
*/
12+
public interface ParentSessionCallbacks {
13+
/**
14+
* Callback when the parent is flushed.
15+
*/
16+
void onParentFlush();
17+
18+
/**
19+
* Callback when the parent is closed.
20+
*/
21+
void onParentClose();
22+
}

hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SessionCreationOptionsAdaptor.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
1313
import org.hibernate.engine.spi.SessionFactoryImplementor;
1414
import org.hibernate.engine.spi.TransactionCompletionCallbacksImplementor;
15+
import org.hibernate.internal.AbstractSharedSessionContract;
1516
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
1617
import org.hibernate.resource.jdbc.spi.StatementInspector;
1718
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
@@ -31,7 +32,8 @@
3132
*/
3233
public record SessionCreationOptionsAdaptor(
3334
SessionFactoryImplementor factory,
34-
CommonSharedSessionCreationOptions options)
35+
CommonSharedSessionCreationOptions options,
36+
AbstractSharedSessionContract originalSession)
3537
implements SharedSessionCreationOptions {
3638

3739
@Override
@@ -143,4 +145,19 @@ public Transaction getTransaction() {
143145
public TransactionCompletionCallbacksImplementor getTransactionCompletionCallbacks() {
144146
return options.getTransactionCompletionCallbacksImplementor();
145147
}
148+
149+
@Override
150+
public void registerParentSessionCallbacks(ParentSessionCallbacks callbacks) {
151+
originalSession.getEventListenerManager().addListener( new SessionEventListener() {
152+
@Override
153+
public void flushStart() {
154+
callbacks.onParentFlush();
155+
}
156+
157+
@Override
158+
public void end() {
159+
callbacks.onParentClose();
160+
}
161+
} );
162+
}
146163
}

hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedSessionBuilderImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,22 @@ public SharedSessionBuilderImplementor subselectFetchEnabled(boolean subselectFe
259259
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
260260
// SharedSessionCreationOptions
261261

262+
263+
@Override
264+
public void registerParentSessionCallbacks(ParentSessionCallbacks callbacks) {
265+
original.getEventListenerManager().addListener( new SessionEventListener() {
266+
@Override
267+
public void flushStart() {
268+
callbacks.onParentFlush();
269+
}
270+
271+
@Override
272+
public void end() {
273+
callbacks.onParentClose();
274+
}
275+
} );
276+
}
277+
262278
@Override
263279
public boolean isTransactionCoordinatorShared() {
264280
return shareTransactionContext;

hibernate-core/src/main/java/org/hibernate/engine/creation/internal/SharedSessionCreationOptions.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ public interface SharedSessionCreationOptions extends SessionCreationOptions {
2626
JdbcCoordinator getJdbcCoordinator();
2727
Transaction getTransaction();
2828
TransactionCompletionCallbacksImplementor getTransactionCompletionCallbacks();
29+
30+
/**
31+
* Registers callbacks for the child session to integrate with events of the parent session.
32+
*/
33+
void registerParentSessionCallbacks(ParentSessionCallbacks callbacks);
34+
2935
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import jakarta.persistence.criteria.CriteriaDelete;
1111
import jakarta.persistence.criteria.CriteriaQuery;
1212
import jakarta.persistence.criteria.CriteriaUpdate;
13-
1413
import org.checkerframework.checker.nullness.qual.Nullable;
1514
import org.hibernate.CacheMode;
1615
import org.hibernate.EntityNameResolver;
@@ -28,6 +27,7 @@
2827
import org.hibernate.bytecode.enhance.spi.interceptor.SessionAssociationMarkers;
2928
import org.hibernate.cache.spi.CacheTransactionSynchronization;
3029
import org.hibernate.dialect.Dialect;
30+
import org.hibernate.engine.creation.internal.ParentSessionCallbacks;
3131
import org.hibernate.engine.creation.internal.SessionCreationOptions;
3232
import org.hibernate.engine.creation.internal.SessionCreationOptionsAdaptor;
3333
import org.hibernate.engine.creation.internal.SharedSessionBuilderImpl;
@@ -225,6 +225,17 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation
225225
jdbcSessionContext = createJdbcSessionContext( statementInspector );
226226
logInconsistentOptions( sharedOptions );
227227
addSharedSessionTransactionObserver( transactionCoordinator );
228+
sharedOptions.registerParentSessionCallbacks( new ParentSessionCallbacks() {
229+
@Override
230+
public void onParentFlush() {
231+
propagateFlush();
232+
}
233+
234+
@Override
235+
public void onParentClose() {
236+
propagateClose();
237+
}
238+
} );
228239
}
229240
else {
230241
isTransactionCoordinatorShared = false;
@@ -249,7 +260,7 @@ public SharedStatelessSessionBuilder statelessWithOptions() {
249260
@Override
250261
protected StatelessSessionImplementor createStatelessSession() {
251262
return new StatelessSessionImpl( factory,
252-
new SessionCreationOptionsAdaptor( factory, this ) );
263+
new SessionCreationOptionsAdaptor( factory, this, AbstractSharedSessionContract.this ) );
253264
}
254265
};
255266
}
@@ -494,6 +505,13 @@ public boolean isClosed() {
494505

495506
@Override
496507
public void close() {
508+
if ( isTransactionCoordinatorShared ) {
509+
// Perform an auto-flush -
510+
// This deals with the natural usage pattern of a child Session
511+
// used with a try-with-resource block
512+
propagateFlush();
513+
}
514+
497515
if ( !closed || waitingForAutoClose ) {
498516
try {
499517
delayedAfterCompletion();
@@ -538,6 +556,10 @@ protected void setClosed() {
538556
cleanupOnClose();
539557
}
540558

559+
protected abstract void propagateFlush();
560+
561+
protected abstract void propagateClose();
562+
541563
protected void checkBeforeClosingJdbcCoordinator() {
542564
}
543565

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
196196

197197
identifierRollbackEnabled = options.isIdentifierRollbackEnabled();
198198

199-
setUpTransactionCompletionProcesses( options, actionQueue );
199+
setUpTransactionCompletionProcesses( options, actionQueue, this );
200200

201201
loadQueryInfluencers = new LoadQueryInfluencers( factory, options );
202202

@@ -224,12 +224,23 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
224224
}
225225
}
226226

227-
private static void setUpTransactionCompletionProcesses(SessionCreationOptions options, ActionQueue actionQueue) {
227+
private static void setUpTransactionCompletionProcesses(
228+
SessionCreationOptions options,
229+
ActionQueue actionQueue,
230+
SessionImpl childSession) {
228231
if ( options instanceof SharedSessionCreationOptions sharedOptions
229232
&& sharedOptions.isTransactionCoordinatorShared() ) {
230233
final var callbacks = sharedOptions.getTransactionCompletionCallbacks();
231234
if ( callbacks != null ) {
232235
actionQueue.setTransactionCompletionCallbacks( callbacks, true );
236+
// // register a callback with the child session to propagate auto flushing
237+
// callbacks.registerCallback( session -> {
238+
// // NOTE: `session` here is the parent
239+
// assert session != childSession;
240+
// if ( !childSession.isClosed() && childSession.getHibernateFlushMode() != FlushMode.MANUAL ) {
241+
// childSession.triggerChildAutoFlush();
242+
// }
243+
// } );
233244
}
234245
}
235246
}
@@ -1424,6 +1435,36 @@ private void fireFlush() {
14241435
}
14251436
}
14261437

1438+
/**
1439+
* Used for auto flushing shared/child session as part of the parent session's auto flush.
1440+
*/
1441+
@Override
1442+
public void propagateFlush() {
1443+
if ( isClosed() ) {
1444+
return;
1445+
}
1446+
if ( !isReadOnly() ) {
1447+
try {
1448+
SESSION_LOGGER.automaticallyFlushingChildSession();
1449+
eventListenerGroups.eventListenerGroup_FLUSH
1450+
.fireEventOnEachListener( new FlushEvent( this ),
1451+
FlushEventListener::onFlush );
1452+
}
1453+
catch (RuntimeException e) {
1454+
throw getExceptionConverter().convert( e );
1455+
}
1456+
}
1457+
}
1458+
1459+
@Override
1460+
public void propagateClose() {
1461+
if ( isClosed() ) {
1462+
return;
1463+
}
1464+
SESSION_LOGGER.automaticallyClosingChildSession();
1465+
closeWithoutOpenChecks();
1466+
}
1467+
14271468
@Override
14281469
public void setHibernateFlushMode(FlushMode flushMode) {
14291470
this.flushMode = flushMode;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,19 @@ public interface SessionLogging extends BasicLogger {
7676
@Message("Automatically flushing session")
7777
void automaticallyFlushingSession();
7878

79+
@LogMessage(level = TRACE)
80+
@Message("Automatically flushing child session")
81+
void automaticallyFlushingChildSession();
82+
7983
@LogMessage(level = TRACE)
8084
@Message("Automatically closing session")
8185
void automaticallyClosingSession();
8286

87+
88+
@LogMessage(level = TRACE)
89+
@Message("Automatically closing child session")
90+
void automaticallyClosingChildSession();
91+
8392
@LogMessage(level = TRACE)
8493
@Message("%s remove orphan before updates: [%s]")
8594
void removeOrphanBeforeUpdates(String timing, String entityInfo);

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

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,28 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
121121
private final boolean connectionProvided;
122122
private final TransactionCompletionCallbacksImplementor transactionCompletionCallbacks;
123123

124+
private final FlushMode flushMode;
124125
private final EventListenerGroups eventListenerGroups;
125126

126127
public StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
127128
super( factory, options );
128129
connectionProvided = options.getConnection() != null;
129-
transactionCompletionCallbacks =
130-
options instanceof SharedSessionCreationOptions sharedOptions
131-
&& sharedOptions.isTransactionCoordinatorShared()
132-
? sharedOptions.getTransactionCompletionCallbacks()
133-
: new TransactionCompletionCallbacksImpl( this );
130+
if ( options instanceof SharedSessionCreationOptions sharedOptions
131+
&& sharedOptions.isTransactionCoordinatorShared() ) {
132+
transactionCompletionCallbacks = sharedOptions.getTransactionCompletionCallbacks();
133+
// // register a callback with the child session to propagate auto flushing
134+
// transactionCompletionCallbacks.registerCallback( session -> {
135+
// // NOTE: `session` here is the parent
136+
// if ( !isClosed() ) {
137+
// triggerChildAutoFlush();
138+
// }
139+
// } );
140+
flushMode = FlushMode.AUTO;
141+
}
142+
else {
143+
transactionCompletionCallbacks = new TransactionCompletionCallbacksImpl( this );
144+
flushMode = FlushMode.MANUAL;
145+
}
134146
temporaryPersistenceContext = createPersistenceContext( this );
135147
influencers = new LoadQueryInfluencers( getFactory() );
136148
eventListenerGroups = factory.getEventListenerGroups();
@@ -147,7 +159,8 @@ public boolean shouldAutoJoinTransaction() {
147159

148160
@Override
149161
public FlushMode getHibernateFlushMode() {
150-
return FlushMode.MANUAL;
162+
// NOTE: only ever *not* MANUAL when this is a "child session"
163+
return flushMode;
151164
}
152165

153166
private StatisticsImplementor getStatistics() {
@@ -1199,6 +1212,24 @@ private void managedFlush() {
11991212
getJdbcCoordinator().executeBatch();
12001213
}
12011214

1215+
@Override
1216+
public void propagateFlush() {
1217+
if ( isClosed() ) {
1218+
return;
1219+
}
1220+
SESSION_LOGGER.automaticallyFlushingChildSession();
1221+
getJdbcCoordinator().executeBatch();
1222+
}
1223+
1224+
@Override
1225+
protected void propagateClose() {
1226+
if ( isClosed() ) {
1227+
return;
1228+
}
1229+
SESSION_LOGGER.automaticallyClosingChildSession();
1230+
close();
1231+
}
1232+
12021233
@Override
12031234
public String bestGuessEntityName(Object object) {
12041235
final var lazyInitializer = extractLazyInitializer( object );

hibernate-core/src/test/java/org/hibernate/orm/test/filter/AbstractStatefulStatelessFilterTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44
*/
55
package org.hibernate.orm.test.filter;
66

7-
import java.util.List;
8-
import java.util.function.BiConsumer;
9-
import java.util.function.Consumer;
10-
11-
import org.hibernate.StatelessSession;
127
import org.hibernate.engine.spi.SessionImplementor;
13-
8+
import org.hibernate.engine.spi.StatelessSessionImplementor;
149
import org.hibernate.testing.orm.junit.SessionFactory;
1510
import org.hibernate.testing.orm.junit.SessionFactoryScope;
1611
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
1712
import org.junit.jupiter.params.provider.Arguments;
1813

14+
import java.util.List;
15+
import java.util.function.BiConsumer;
16+
import java.util.function.Consumer;
17+
1918
@SessionFactory
2019
public abstract class AbstractStatefulStatelessFilterTest implements SessionFactoryScopeAware {
2120

@@ -29,7 +28,7 @@ public void injectSessionFactoryScope(SessionFactoryScope scope) {
2928
protected List<? extends Arguments> transactionKind() {
3029
// We want to test both regular and stateless session:
3130
BiConsumer<SessionFactoryScope, Consumer<SessionImplementor>> kind1 = SessionFactoryScope::inTransaction;
32-
BiConsumer<SessionFactoryScope, Consumer<StatelessSession>> kind2 = SessionFactoryScope::inStatelessTransaction;
31+
BiConsumer<SessionFactoryScope, Consumer<StatelessSessionImplementor>> kind2 = SessionFactoryScope::inStatelessTransaction;
3332
return List.of(
3433
Arguments.of( kind1 ),
3534
Arguments.of( kind2 )

hibernate-core/src/test/java/org/hibernate/orm/test/filter/subclass/joined2/JoinedInheritanceFilterTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hibernate.annotations.ParamDef;
1818
import org.hibernate.engine.spi.SessionImplementor;
1919

20+
import org.hibernate.engine.spi.StatelessSessionImplementor;
2021
import org.hibernate.testing.orm.junit.JiraKey;
2122
import org.hibernate.testing.orm.junit.DomainModel;
2223
import org.hibernate.testing.orm.junit.SessionFactory;
@@ -61,7 +62,7 @@ List<? extends Arguments> transactionKind() {
6162
// We want to test both regular and stateless session:
6263
BiConsumer<SessionFactoryScope, Consumer<SessionImplementor>> kind1 = SessionFactoryScope::inTransaction;
6364
TriFunction<Session, Class<?>, Object, Object> find1 = Session::get;
64-
BiConsumer<SessionFactoryScope, Consumer<StatelessSession>> kind2 = SessionFactoryScope::inStatelessTransaction;
65+
BiConsumer<SessionFactoryScope, Consumer<StatelessSessionImplementor>> kind2 = SessionFactoryScope::inStatelessTransaction;
6566
TriFunction<StatelessSession, Class<?>, Object, Object> find2 = StatelessSession::get;
6667
return List.of(
6768
Arguments.of( kind1, find1 ),

0 commit comments

Comments
 (0)