SpringSessionContext for Hibernate 4 supports lazily bound Session for propagation SUPPORTS

Issue: SPR-9020
This commit is contained in:
Juergen Hoeller 2014-08-09 18:28:30 +02:00
parent 562916b69c
commit 5cbb1fc498
4 changed files with 102 additions and 18 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -102,7 +102,9 @@ public abstract class SessionFactoryUtils {
public static DataSource getDataSource(SessionFactory sessionFactory) {
if (getConnectionProviderMethod != null && sessionFactory instanceof SessionFactoryImplementor) {
Wrapped cp = (Wrapped) ReflectionUtils.invokeMethod(getConnectionProviderMethod, sessionFactory);
return cp.unwrap(DataSource.class);
if (cp != null) {
return cp.unwrap(DataSource.class);
}
}
return null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,7 +17,11 @@
package org.springframework.orm.hibernate4;
import java.lang.reflect.Method;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
@ -44,6 +48,8 @@ public class SpringSessionContext implements CurrentSessionContext {
private final SessionFactoryImplementor sessionFactory;
private TransactionManager transactionManager;
private CurrentSessionContext jtaSessionContext;
@ -56,13 +62,14 @@ public class SpringSessionContext implements CurrentSessionContext {
try {
Object jtaPlatform = sessionFactory.getServiceRegistry().getService(ConfigurableJtaPlatform.jtaPlatformClass);
Method rtmMethod = ConfigurableJtaPlatform.jtaPlatformClass.getMethod("retrieveTransactionManager");
Object transactionManager = ReflectionUtils.invokeMethod(rtmMethod, jtaPlatform);
if (transactionManager != null) {
this.transactionManager = (TransactionManager) ReflectionUtils.invokeMethod(rtmMethod, jtaPlatform);
if (this.transactionManager != null) {
this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory);
}
}
catch (Exception ex) {
throw new IllegalStateException("Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex);
LogFactory.getLog(SpringSessionContext.class).warn(
"Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex);
}
}
@ -82,7 +89,7 @@ public class SpringSessionContext implements CurrentSessionContext {
if (TransactionSynchronizationManager.isSynchronizationActive() &&
!sessionHolder.isSynchronizedWithTransaction()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory));
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
@ -95,15 +102,36 @@ public class SpringSessionContext implements CurrentSessionContext {
}
return session;
}
else if (this.jtaSessionContext != null) {
Session session = this.jtaSessionContext.currentSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session));
if (this.transactionManager != null) {
try {
if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
Session session = this.jtaSessionContext.currentSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session));
}
return session;
}
}
catch (SystemException ex) {
throw new HibernateException("JTA TransactionManager found but status check failed", ex);
}
}
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
}
else {
throw new HibernateException("No Session found for current thread");
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,12 +39,15 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
private final SessionFactory sessionFactory;
private final boolean newSession;
private boolean holderActive = true;
public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory) {
public SpringSessionSynchronization(SessionHolder sessionHolder, SessionFactory sessionFactory, boolean newSession) {
this.sessionHolder = sessionHolder;
this.sessionFactory = sessionFactory;
this.newSession = newSession;
}
private Session getCurrentSession() {
@ -111,6 +114,12 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
}
// Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
session.disconnect();
// Unbind at this point if it's a new Session...
if (this.newSession) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
this.holderActive = false;
}
}
@Override
@ -128,6 +137,10 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
}
finally {
this.sessionHolder.setSynchronizedWithTransaction(false);
// Call close() at this point if it's a new Session...
if (this.newSession) {
SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
}
}
}

View File

@ -23,7 +23,6 @@ import java.sql.Savepoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
@ -38,13 +37,12 @@ import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory;
import org.hibernate.exception.ConstraintViolationException;
import org.junit.After;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.beans.factory.BeanFactory;
@ -495,6 +493,49 @@ public class HibernateTransactionManagerTests {
ordered.verify(session).close();
}
@Test
public void testTransactionWithPropagationSupportsAndCurrentSession() throws Exception {
final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
final Session session = mock(Session.class);
given(sf.openSession()).willReturn(session);
given(session.getSessionFactory()).willReturn(sf);
given(session.getFlushMode()).willReturn(FlushMode.MANUAL);
LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean() {
@Override
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return sf;
}
};
lsfb.afterPropertiesSet();
final SessionFactory sfProxy = lsfb.getObject();
PlatformTransactionManager tm = new HibernateTransactionManager(sfProxy);
TransactionTemplate tt = new TransactionTemplate(tm);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sfProxy));
tt.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sfProxy));
assertTrue("Is not new transaction", !status.isNewTransaction());
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
Session session = new SpringSessionContext(sf).currentSession();
assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sfProxy));
session.flush();
return null;
}
});
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sfProxy));
InOrder ordered = inOrder(session);
ordered.verify(session).flush();
ordered.verify(session).close();
}
@Test
public void testTransactionWithPropagationSupportsAndInnerTransaction() throws Exception {
final SessionFactory sf = mock(SessionFactory.class);
@ -735,7 +776,7 @@ public class HibernateTransactionManagerTests {
catch (DataIntegrityViolationException ex) {
// expected
assertEquals(rootCause, ex.getCause());
assertTrue(ex.getMessage().indexOf("mymsg") != -1);
assertTrue(ex.getMessage().contains("mymsg"));
}
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));