HibernateTransactionManager supports result access after completion
Issue: SPR-12349
This commit is contained in:
parent
b16048b061
commit
49f3a6beff
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.orm.hibernate4;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.hibernate.ConnectionReleaseMode;
|
||||
|
|
@ -117,6 +118,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
|
||||
private boolean prepareConnection = true;
|
||||
|
||||
private boolean allowResultAccessAfterCompletion = false;
|
||||
|
||||
private boolean hibernateManagedSession = false;
|
||||
|
||||
private Object entityInterceptor;
|
||||
|
|
@ -229,6 +232,21 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
this.prepareConnection = prepareConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to allow result access after completion, typically via Hibernate's
|
||||
* ScrollableResults mechanism.
|
||||
* <p>Default is "false". Turning this flag on enforces over-commit holdability on the
|
||||
* underlying JDBC Connection (if {@link #prepareConnection "prepareConnection"} is on)
|
||||
* and skips the disconnect-on-completion step.
|
||||
* @since 4.1.2
|
||||
* @see java.sql.Connection#setHoldability
|
||||
* @see ResultSet#HOLD_CURSORS_OVER_COMMIT
|
||||
* @see #disconnectOnCompletion(Session)
|
||||
*/
|
||||
public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) {
|
||||
this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to operate on a Hibernate-managed Session instead of a
|
||||
* Spring-managed Session, that is, whether to obtain the Session through
|
||||
|
|
@ -432,6 +450,13 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
Connection con = ((SessionImplementor) session).connection();
|
||||
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
|
||||
txObject.setPreviousIsolationLevel(previousIsolationLevel);
|
||||
if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
|
||||
int currentHoldability = con.getHoldability();
|
||||
if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
|
||||
txObject.setPreviousHoldability(currentHoldability);
|
||||
con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Not allowed to change the transaction settings of the JDBC Connection.
|
||||
|
|
@ -625,11 +650,18 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
// Else, we need to rely on the connection pool to perform proper cleanup.
|
||||
try {
|
||||
Connection con = ((SessionImplementor) session).connection();
|
||||
Integer previousHoldability = txObject.getPreviousHoldability();
|
||||
if (previousHoldability != null) {
|
||||
con.setHoldability(previousHoldability);
|
||||
}
|
||||
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
|
||||
}
|
||||
catch (HibernateException ex) {
|
||||
logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not reset JDBC Connection after transaction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (txObject.isNewSession()) {
|
||||
|
|
@ -645,13 +677,26 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
|
||||
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
|
||||
}
|
||||
if (!this.hibernateManagedSession) {
|
||||
session.disconnect();
|
||||
if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
|
||||
disconnectOnCompletion(session);
|
||||
}
|
||||
}
|
||||
txObject.getSessionHolder().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a pre-existing Hibernate Session on transaction completion,
|
||||
* returning its database connection but preserving its entity state.
|
||||
* <p>The default implementation simply calls {@link Session#disconnect()}.
|
||||
* Subclasses may override this with a no-op or with fine-tuned disconnection logic.
|
||||
* @param session the Hibernate Session to disconnect
|
||||
* @since 4.1.2
|
||||
* @see org.hibernate.Session#disconnect()
|
||||
*/
|
||||
protected void disconnectOnCompletion(Session session) {
|
||||
session.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given Hibernate Session will always hold the same
|
||||
* JDBC Connection. This is used to check whether the transaction manager
|
||||
|
|
@ -698,6 +743,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
|
||||
private boolean newSession;
|
||||
|
||||
private Integer previousHoldability;
|
||||
|
||||
public void setSession(Session session) {
|
||||
this.sessionHolder = new SessionHolder(session);
|
||||
this.newSessionHolder = true;
|
||||
|
|
@ -728,6 +775,14 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
|||
return this.newSession;
|
||||
}
|
||||
|
||||
public void setPreviousHoldability(Integer previousHoldability) {
|
||||
this.previousHoldability = previousHoldability;
|
||||
}
|
||||
|
||||
public Integer getPreviousHoldability() {
|
||||
return this.previousHoldability;
|
||||
}
|
||||
|
||||
public boolean hasSpringManagedTransaction() {
|
||||
return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.springframework.orm.hibernate4;
|
|||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -624,6 +625,7 @@ public class HibernateTransactionManagerTests {
|
|||
|
||||
HibernateTransactionManager tm = new HibernateTransactionManager(sf);
|
||||
tm.setEntityInterceptor(entityInterceptor);
|
||||
tm.setAllowResultAccessAfterCompletion(true);
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
|
||||
|
|
@ -843,6 +845,66 @@ public class HibernateTransactionManagerTests {
|
|||
verify(session).disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCommitWithPreBoundAndResultAccessAfterCommit() throws Exception {
|
||||
final DataSource ds = mock(DataSource.class);
|
||||
Connection con = mock(Connection.class);
|
||||
final SessionFactory sf = mock(SessionFactory.class);
|
||||
final ImplementingSession session = mock(ImplementingSession.class);
|
||||
Transaction tx = mock(Transaction.class);
|
||||
|
||||
given(session.beginTransaction()).willReturn(tx);
|
||||
given(session.isOpen()).willReturn(true);
|
||||
given(session.getFlushMode()).willReturn(FlushMode.MANUAL);
|
||||
given(session.connection()).willReturn(con);
|
||||
given(con.getTransactionIsolation()).willReturn(Connection.TRANSACTION_READ_COMMITTED);
|
||||
given(con.getHoldability()).willReturn(ResultSet.CLOSE_CURSORS_AT_COMMIT);
|
||||
given(session.isConnected()).willReturn(true);
|
||||
|
||||
HibernateTransactionManager tm = new HibernateTransactionManager();
|
||||
tm.setSessionFactory(sf);
|
||||
tm.setDataSource(ds);
|
||||
tm.setAllowResultAccessAfterCompletion(true);
|
||||
TransactionTemplate tt = new TransactionTemplate(tm);
|
||||
tt.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
final List l = new ArrayList();
|
||||
l.add("test");
|
||||
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
|
||||
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
|
||||
TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
|
||||
assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
|
||||
|
||||
Object result = tt.execute(new TransactionCallback() {
|
||||
@Override
|
||||
public Object doInTransaction(TransactionStatus status) {
|
||||
assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
|
||||
assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
|
||||
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
|
||||
assertTrue("Has thread transaction", sessionHolder.getTransaction() != null);
|
||||
Session sess = ((SessionHolder) TransactionSynchronizationManager.getResource(sf)).getSession();
|
||||
assertEquals(session, sess);
|
||||
return l;
|
||||
}
|
||||
});
|
||||
assertTrue("Correct result list", result == l);
|
||||
|
||||
assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
|
||||
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
|
||||
assertTrue("Hasn't thread transaction", sessionHolder.getTransaction() == null);
|
||||
TransactionSynchronizationManager.unbindResource(sf);
|
||||
assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
|
||||
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
|
||||
|
||||
InOrder ordered = inOrder(session, con);
|
||||
ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
|
||||
ordered.verify(con).setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
|
||||
ordered.verify(session).setFlushMode(FlushMode.AUTO);
|
||||
ordered.verify(con).setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
|
||||
ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
||||
ordered.verify(session).setFlushMode(FlushMode.MANUAL);
|
||||
verify(tx).commit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionRollbackWithPreBound() throws Exception {
|
||||
final DataSource ds = mock(DataSource.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue