Skip to content

Commit 03a8933

Browse files
committed
Add transactional support for StatelessSession (next to regular Session)
Exposes JPA-style shared proxy instances through LocalSessionFactoryBean. Closes gh-7184
1 parent ec87d90 commit 03a8933

File tree

9 files changed

+424
-73
lines changed

9 files changed

+424
-73
lines changed

spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
*/
3838
public class EntityManagerHolder extends ResourceHolderSupport {
3939

40-
private final @Nullable EntityManager entityManager;
40+
protected @Nullable EntityManager entityManager;
4141

4242
private boolean transactionActive;
4343

@@ -78,4 +78,8 @@ public void clear() {
7878
this.savepointManager = null;
7979
}
8080

81+
protected void closeAll() {
82+
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
83+
}
84+
8185
}

spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -640,11 +640,8 @@ protected void doCleanupAfterCompletion(Object transaction) {
640640

641641
// Remove the entity manager holder from the thread.
642642
if (txObject.isNewEntityManagerHolder()) {
643-
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
644-
if (logger.isDebugEnabled()) {
645-
logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
646-
}
647-
EntityManagerFactoryUtils.closeEntityManager(em);
643+
logger.debug("Closing JPA EntityManager after transaction");
644+
txObject.getEntityManagerHolder().closeAll();
648645
}
649646
else {
650647
logger.debug("Not closing pre-bound JPA EntityManager after transaction");

spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/HibernateTransactionManager.java

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.orm.jpa.hibernate;
1818

1919
import java.sql.Connection;
20-
import java.util.Map;
2120
import java.util.function.Consumer;
2221

2322
import javax.sql.DataSource;
@@ -29,12 +28,8 @@
2928
import org.hibernate.Session;
3029
import org.hibernate.SessionFactory;
3130
import org.hibernate.Transaction;
32-
import org.hibernate.cfg.Environment;
33-
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
34-
import org.hibernate.engine.spi.SessionFactoryImplementor;
3531
import org.hibernate.engine.spi.SessionImplementor;
3632
import org.hibernate.resource.transaction.spi.TransactionStatus;
37-
import org.hibernate.service.UnknownServiceException;
3833
import org.jspecify.annotations.Nullable;
3934

4035
import org.springframework.beans.BeansException;
@@ -372,7 +367,7 @@ public void afterPropertiesSet() {
372367

373368
// Check for SessionFactory's DataSource.
374369
if (this.autodetectDataSource && getDataSource() == null) {
375-
DataSource sfds = determineDataSource();
370+
DataSource sfds = SpringSessionContext.determineDataSource(obtainSessionFactory());
376371
if (sfds != null) {
377372
// Use the SessionFactory's DataSource for exposing transactions to JDBC code.
378373
if (logger.isDebugEnabled()) {
@@ -384,36 +379,6 @@ public void afterPropertiesSet() {
384379
}
385380
}
386381

387-
/**
388-
* Determine the DataSource of the given SessionFactory.
389-
* @return the DataSource, or {@code null} if none found
390-
* @see ConnectionProvider
391-
*/
392-
protected @Nullable DataSource determineDataSource() {
393-
SessionFactory sessionFactory = obtainSessionFactory();
394-
Map<String, Object> props = sessionFactory.getProperties();
395-
if (props != null) {
396-
Object dataSourceValue = props.get(Environment.JAKARTA_NON_JTA_DATASOURCE);
397-
if (dataSourceValue instanceof DataSource dataSourceToUse) {
398-
return dataSourceToUse;
399-
}
400-
}
401-
if (sessionFactory instanceof SessionFactoryImplementor sfi) {
402-
try {
403-
ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
404-
if (cp != null) {
405-
return cp.unwrap(DataSource.class);
406-
}
407-
}
408-
catch (UnknownServiceException ex) {
409-
if (logger.isDebugEnabled()) {
410-
logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex);
411-
}
412-
}
413-
}
414-
return null;
415-
}
416-
417382

418383
@Override
419384
public Object getResourceFactory() {
@@ -735,7 +700,7 @@ protected void doCleanupAfterCompletion(Object transaction) {
735700
if (logger.isDebugEnabled()) {
736701
logger.debug("Closing Hibernate Session [" + session + "] after transaction");
737702
}
738-
EntityManagerFactoryUtils.closeEntityManager(session);
703+
txObject.getSessionHolder().closeAll();
739704
}
740705
else {
741706
if (logger.isDebugEnabled()) {

spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/LocalSessionFactoryBean.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import javax.sql.DataSource;
2424

2525
import org.hibernate.Interceptor;
26+
import org.hibernate.Session;
2627
import org.hibernate.SessionFactory;
28+
import org.hibernate.StatelessSession;
2729
import org.hibernate.boot.MetadataSources;
2830
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
2931
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
@@ -41,6 +43,7 @@
4143
import org.springframework.beans.factory.DisposableBean;
4244
import org.springframework.beans.factory.FactoryBean;
4345
import org.springframework.beans.factory.InitializingBean;
46+
import org.springframework.beans.factory.SmartFactoryBean;
4447
import org.springframework.beans.factory.SmartInitializingSingleton;
4548
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4649
import org.springframework.context.ResourceLoaderAware;
@@ -77,7 +80,7 @@
7780
* @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
7881
*/
7982
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
80-
implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware,
83+
implements SmartFactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware,
8184
InitializingBean, SmartInitializingSingleton, DisposableBean {
8285

8386
private @Nullable DataSource dataSource;
@@ -134,6 +137,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
134137

135138
private @Nullable SessionFactory sessionFactory;
136139

140+
private @Nullable Session sharedSession;
141+
142+
private @Nullable StatelessSession sharedStatelessSession;
143+
137144

138145
/**
139146
* Set the DataSource to be used by the SessionFactory.
@@ -565,6 +572,8 @@ public void afterPropertiesSet() throws IOException {
565572
// Build SessionFactory instance.
566573
this.configuration = sfb;
567574
this.sessionFactory = buildSessionFactory(sfb);
575+
this.sharedSession = SharedSessionCreator.createSharedSession(this.sessionFactory);
576+
this.sharedStatelessSession = SharedSessionCreator.createSharedStatelessSession(this.sessionFactory);
568577
}
569578

570579
@Override
@@ -614,9 +623,24 @@ public Class<?> getObjectType() {
614623
return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
615624
}
616625

626+
/**
627+
* Return either the singleton SessionFactory or a shared (Stateless)Session proxy.
628+
*/
629+
@Override
630+
public <S> @Nullable S getObject(Class<S> type) throws Exception {
631+
if (Session.class.isAssignableFrom(type)) {
632+
return type.cast(this.sharedSession);
633+
}
634+
if (StatelessSession.class.isAssignableFrom(type)) {
635+
return type.cast(this.sharedStatelessSession);
636+
}
637+
return SmartFactoryBean.super.getObject(type);
638+
}
639+
617640
@Override
618-
public boolean isSingleton() {
619-
return true;
641+
public boolean supportsType(Class<?> type) {
642+
return (type == Session.class || type == StatelessSession.class ||
643+
SmartFactoryBean.super.supportsType(type));
620644
}
621645

622646

spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import org.hibernate.FlushMode;
2020
import org.hibernate.Session;
21+
import org.hibernate.StatelessSession;
2122
import org.hibernate.Transaction;
2223
import org.jspecify.annotations.Nullable;
2324

2425
import org.springframework.orm.jpa.EntityManagerHolder;
26+
import org.springframework.util.Assert;
2527

2628
/**
2729
* Resource holder wrapping a Hibernate {@link Session} (plus an optional {@link Transaction}).
@@ -37,6 +39,8 @@
3739
*/
3840
class SessionHolder extends EntityManagerHolder {
3941

42+
private @Nullable StatelessSession statelessSession;
43+
4044
private @Nullable Transaction transaction;
4145

4246
private @Nullable FlushMode previousFlushMode;
@@ -46,11 +50,37 @@ public SessionHolder(Session session) {
4650
super(session);
4751
}
4852

53+
public SessionHolder(StatelessSession session) {
54+
super(null);
55+
this.statelessSession = session;
56+
}
57+
58+
59+
public void setSession(Session session) {
60+
this.entityManager = session;
61+
}
4962

5063
public Session getSession() {
5164
return (Session) getEntityManager();
5265
}
5366

67+
public boolean hasSession() {
68+
return (this.entityManager != null);
69+
}
70+
71+
public void setStatelessSession(StatelessSession statelessSession) {
72+
this.statelessSession = statelessSession;
73+
}
74+
75+
public StatelessSession getStatelessSession() {
76+
Assert.state(this.statelessSession != null, "No StatelessSession available");
77+
return this.statelessSession;
78+
}
79+
80+
public boolean hasStatelessSession() {
81+
return (this.statelessSession != null);
82+
}
83+
5484
public void setTransaction(@Nullable Transaction transaction) {
5585
this.transaction = transaction;
5686
setTransactionActive(transaction != null);
@@ -76,4 +106,12 @@ public void clear() {
76106
this.previousFlushMode = null;
77107
}
78108

109+
@Override
110+
protected void closeAll() {
111+
super.closeAll();
112+
if (this.statelessSession != null && this.statelessSession.isOpen()) {
113+
this.statelessSession.close();
114+
}
115+
}
116+
79117
}

0 commit comments

Comments
 (0)