added "flush()" method to TransactionStatus and TransactionSynchronization interfaces; test context manager automatically flushes transactions before rolling back; general polishing of transaction management code

This commit is contained in:
Juergen Hoeller 2009-02-19 00:24:05 +00:00
parent dd7d299aa4
commit 4cc42bf16f
34 changed files with 415 additions and 134 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -345,7 +345,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
}
public boolean isNewConnectionHolder() {
return newConnectionHolder;
return this.newConnectionHolder;
}
public boolean hasTransaction() {
@ -357,7 +357,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
}
public boolean isMustRestoreAutoCommit() {
return mustRestoreAutoCommit;
return this.mustRestoreAutoCommit;
}
public void setRollbackOnly() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -87,6 +87,10 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager,
return this.savepointAllowed;
}
public void flush() {
// no-op
}
//---------------------------------------------------------------------
// Implementation of SavepointManager

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -1958,6 +1958,9 @@ public class DataSourceTransactionManagerTests extends TestCase {
public void resume() {
}
public void flush() {
}
public void beforeCommit(boolean readOnly) {
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
fail("Should never be called");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -30,7 +30,6 @@ import javax.jms.TopicSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
@ -387,7 +386,7 @@ public abstract class ConnectionFactoryUtils {
* (e.g. when participating in a JtaTransactionManager transaction).
* @see org.springframework.transaction.jta.JtaTransactionManager
*/
private static class JmsResourceSynchronization extends ResourceHolderSynchronization {
private static class JmsResourceSynchronization extends ResourceHolderSynchronization<JmsResourceHolder, Object> {
private final boolean transacted;
@ -400,17 +399,17 @@ public abstract class ConnectionFactoryUtils {
return !this.transacted;
}
protected void processResourceAfterCommit(ResourceHolder resourceHolder) {
protected void processResourceAfterCommit(JmsResourceHolder resourceHolder) {
try {
((JmsResourceHolder) resourceHolder).commitAll();
resourceHolder.commitAll();
}
catch (JMSException ex) {
throw new SynchedLocalTransactionFailedException("Local JMS transaction failed to commit", ex);
}
}
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
((JmsResourceHolder) resourceHolder).closeAll();
protected void releaseResource(JmsResourceHolder resourceHolder, Object resourceKey) {
resourceHolder.closeAll();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -316,6 +316,10 @@ public class JmsTransactionManager extends AbstractPlatformTransactionManager
public boolean isRollbackOnly() {
return this.resourceHolder.isRollbackOnly();
}
public void flush() {
// no-op
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -1160,21 +1160,20 @@ public class HibernateTemplate extends HibernateAccessor implements HibernateOpe
/**
* Check whether write operations are allowed on the given Session.
* <p>Default implementation throws an InvalidDataAccessApiUsageException in
* case of <code>FlushMode.NEVER/MANUAL</code>. Can be overridden in subclasses.
* case of <code>FlushMode.MANUAL</code>. Can be overridden in subclasses.
* @param session current Hibernate Session
* @throws InvalidDataAccessApiUsageException if write operations are not allowed
* @see #setCheckWriteOperations
* @see #getFlushMode()
* @see #FLUSH_EAGER
* @see org.hibernate.Session#getFlushMode()
* @see org.hibernate.FlushMode#NEVER
* @see org.hibernate.FlushMode#MANUAL
*/
protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&
session.getFlushMode().lessThan(FlushMode.COMMIT)) {
throw new InvalidDataAccessApiUsageException(
"Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): "+
"Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+
"Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -828,7 +828,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
* Hibernate transaction object, representing a SessionHolder.
* Used as transaction object by HibernateTransactionManager.
*/
private static class HibernateTransactionObject extends JdbcTransactionObjectSupport {
private class HibernateTransactionObject extends JdbcTransactionObjectSupport {
private SessionHolder sessionHolder;
@ -875,16 +875,25 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
public void setRollbackOnly() {
getSessionHolder().setRollbackOnly();
this.sessionHolder.setRollbackOnly();
if (hasConnectionHolder()) {
getConnectionHolder().setRollbackOnly();
}
}
public boolean isRollbackOnly() {
return getSessionHolder().isRollbackOnly() ||
return this.sessionHolder.isRollbackOnly() ||
(hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
}
public void flush() {
try {
this.sessionHolder.getSession().flush();
}
catch (HibernateException ex) {
throw convertHibernateAccessException(ex);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -124,6 +124,16 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
}
}
public void flush() {
try {
SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request");
getCurrentSession().flush();
}
catch (HibernateException ex) {
throw translateException(ex);
}
}
public void beforeCommit(boolean readOnly) throws DataAccessException {
if (!readOnly) {
Session session = getCurrentSession();
@ -135,17 +145,21 @@ class SpringSessionSynchronization implements TransactionSynchronization, Ordere
session.flush();
}
catch (HibernateException ex) {
if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
JDBCException jdbcEx = (JDBCException) ex;
throw this.jdbcExceptionTranslator.translate(
"Hibernate flushing: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
}
throw SessionFactoryUtils.convertHibernateAccessException(ex);
throw translateException(ex);
}
}
}
}
private DataAccessException translateException(HibernateException ex) {
if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
JDBCException jdbcEx = (JDBCException) ex;
return this.jdbcExceptionTranslator.translate(
"Hibernate flushing: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
}
return SessionFactoryUtils.convertHibernateAccessException(ex);
}
public void beforeCompletion() {
if (this.jtaTransaction != null) {
// Typically in case of a suspended JTA transaction:

View File

@ -144,10 +144,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* {@link org.hibernate.Session}. Only applied in single session mode.
* <p>Can be populated with the corresponding constant name in XML bean
* definitions: e.g. "AUTO".
* <p>The default is "NEVER". Specify "AUTO" if you intend to use
* <p>The default is "MANUAL". Specify "AUTO" if you intend to use
* this filter without service layer transactions.
* @see org.hibernate.Session#setFlushMode
* @see org.hibernate.FlushMode#NEVER
* @see org.hibernate.FlushMode#MANUAL
* @see org.hibernate.FlushMode#AUTO
*/
public void setFlushMode(FlushMode flushMode) {
@ -247,14 +247,14 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* Note that this just applies in single session mode!
* <p>The default implementation delegates to the
* <code>SessionFactoryUtils.getSession</code> method and
* sets the <code>Session</code>'s flush mode to "NEVER".
* sets the <code>Session</code>'s flush mode to "MANUAL".
* <p>Can be overridden in subclasses for creating a Session with a
* custom entity interceptor or JDBC exception translator.
* @param sessionFactory the SessionFactory that this filter uses
* @return the Session to use
* @throws DataAccessResourceFailureException if the Session could not be created
* @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
* @see org.hibernate.FlushMode#NEVER
* @see org.hibernate.FlushMode#MANUAL
*/
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -503,7 +503,7 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager
/**
* Convert the given JDOException to an appropriate exception from the
* <code>org.springframework.dao</code> hierarchy.
* <p>Default implementation delegates to the JdoDialect.
* <p>The default implementation delegates to the JdoDialect.
* May be overridden in subclasses.
* @param ex JDOException that occured
* @return the corresponding DataAccessException instance
@ -518,7 +518,7 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager
* JDO transaction object, representing a PersistenceManagerHolder.
* Used as transaction object by JdoTransactionManager.
*/
private static class JdoTransactionObject extends JdbcTransactionObjectSupport {
private class JdoTransactionObject extends JdbcTransactionObjectSupport {
private PersistenceManagerHolder persistenceManagerHolder;
@ -533,11 +533,11 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager
}
public PersistenceManagerHolder getPersistenceManagerHolder() {
return persistenceManagerHolder;
return this.persistenceManagerHolder;
}
public boolean isNewPersistenceManagerHolder() {
return newPersistenceManagerHolder;
return this.newPersistenceManagerHolder;
}
public boolean hasTransaction() {
@ -550,7 +550,7 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager
}
public Object getTransactionData() {
return transactionData;
return this.transactionData;
}
public void setRollbackOnly() {
@ -567,6 +567,15 @@ public class JdoTransactionManager extends AbstractPlatformTransactionManager
Transaction tx = this.persistenceManagerHolder.getPersistenceManager().currentTransaction();
return tx.getRollbackOnly();
}
public void flush() {
try {
this.persistenceManagerHolder.getPersistenceManager().flush();
}
catch (JDOException ex) {
throw convertJdoAccessException(ex);
}
}
}

View File

@ -38,7 +38,6 @@ import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
@ -293,7 +292,8 @@ public abstract class PersistenceManagerFactoryUtils {
* (e.g. when participating in a JtaTransactionManager transaction).
* @see org.springframework.transaction.jta.JtaTransactionManager
*/
private static class PersistenceManagerSynchronization extends ResourceHolderSynchronization
private static class PersistenceManagerSynchronization
extends ResourceHolderSynchronization<PersistenceManagerHolder, PersistenceManagerFactory>
implements Ordered {
private final boolean newPersistenceManager;
@ -308,15 +308,24 @@ public abstract class PersistenceManagerFactoryUtils {
return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER;
}
@Override
public void flushResource(PersistenceManagerHolder resourceHolder) {
try {
resourceHolder.getPersistenceManager().flush();
}
catch (JDOException ex) {
throw convertJdoAccessException(ex);
}
}
@Override
protected boolean shouldUnbindAtCompletion() {
return this.newPersistenceManager;
}
@Override
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
releasePersistenceManager(((PersistenceManagerHolder) resourceHolder).getPersistenceManager(),
(PersistenceManagerFactory) resourceKey);
protected void releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey) {
releasePersistenceManager(resourceHolder.getPersistenceManager(), resourceKey);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -105,7 +105,7 @@ public abstract class EntityManagerFactoryUtils {
}
// No matching persistence unit found - simply take the EntityManagerFactory
// with the persistence unit name as bean name (by convention).
return (EntityManagerFactory) beanFactory.getBean(unitName, EntityManagerFactory.class);
return beanFactory.getBean(unitName, EntityManagerFactory.class);
}
/**
@ -345,41 +345,62 @@ public abstract class EntityManagerFactoryUtils {
* (e.g. when participating in a JtaTransactionManager transaction).
* @see org.springframework.transaction.jta.JtaTransactionManager
*/
private static class EntityManagerSynchronization extends ResourceHolderSynchronization implements Ordered {
private static class EntityManagerSynchronization
extends ResourceHolderSynchronization<EntityManagerHolder, EntityManagerFactory>
implements Ordered {
private final Object transactionData;
private final JpaDialect jpaDialect;
private final boolean newEntityManager;
public EntityManagerSynchronization(
EntityManagerHolder emHolder, EntityManagerFactory emf, Object transactionData, boolean newEntityManager) {
EntityManagerHolder emHolder, EntityManagerFactory emf, Object txData, boolean newEm) {
super(emHolder, emf);
this.transactionData = transactionData;
this.newEntityManager = newEntityManager;
this.transactionData = txData;
this.jpaDialect = (emf instanceof EntityManagerFactoryInfo ?
((EntityManagerFactoryInfo) emf).getJpaDialect() : null);
this.newEntityManager = newEm;
}
public int getOrder() {
return ENTITY_MANAGER_SYNCHRONIZATION_ORDER;
}
@Override
protected void flushResource(EntityManagerHolder resourceHolder) {
try {
resourceHolder.getEntityManager().flush();
}
catch (RuntimeException ex) {
if (this.jpaDialect != null) {
throw this.jpaDialect.translateExceptionIfPossible(ex);
}
else {
throw convertJpaAccessExceptionIfPossible(ex);
}
}
}
@Override
protected boolean shouldUnbindAtCompletion() {
return this.newEntityManager;
}
@Override
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
closeEntityManager(((EntityManagerHolder) resourceHolder).getEntityManager());
protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) {
closeEntityManager(resourceHolder.getEntityManager());
}
@Override
protected void cleanupResource(ResourceHolder resourceHolder, Object resourceKey, boolean committed) {
protected void cleanupResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey, boolean committed) {
if (!committed) {
// Clear all pending inserts/updates/deletes in the EntityManager.
// Necessary for pre-bound EntityManagers, to avoid inconsistent state.
((EntityManagerHolder) resourceHolder).getEntityManager().clear();
resourceHolder.getEntityManager().clear();
}
cleanupTransaction(this.transactionData, (EntityManagerFactory) resourceKey);
cleanupTransaction(this.transactionData, resourceKey);
}
}

View File

@ -436,7 +436,8 @@ public abstract class ExtendedEntityManagerCreator {
* TransactionSynchronization enlisting an extended EntityManager
* with a current Spring transaction.
*/
private static class ExtendedEntityManagerSynchronization extends ResourceHolderSynchronization
private static class ExtendedEntityManagerSynchronization
extends ResourceHolderSynchronization<EntityManagerHolder, EntityManager>
implements Ordered {
private final EntityManager entityManager;
@ -454,6 +455,16 @@ public abstract class ExtendedEntityManagerCreator {
return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1;
}
@Override
protected void flushResource(EntityManagerHolder resourceHolder) {
try {
this.entityManager.flush();
}
catch (RuntimeException ex) {
throw convertException(ex);
}
}
@Override
protected boolean shouldReleaseBeforeCompletion() {
return false;
@ -467,7 +478,7 @@ public abstract class ExtendedEntityManagerCreator {
this.entityManager.getTransaction().commit();
}
catch (RuntimeException ex) {
throw convertCompletionException(ex);
throw convertException(ex);
}
}
@ -480,12 +491,12 @@ public abstract class ExtendedEntityManagerCreator {
this.entityManager.getTransaction().rollback();
}
catch (RuntimeException ex) {
throw convertCompletionException(ex);
throw convertException(ex);
}
}
}
private RuntimeException convertCompletionException(RuntimeException ex) {
private RuntimeException convertException(RuntimeException ex) {
DataAccessException daex = (this.exceptionTranslator != null) ?
this.exceptionTranslator.translateExceptionIfPossible(ex) :
EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -19,7 +19,6 @@ package org.springframework.orm.jpa;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
@ -553,7 +552,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager
* JPA transaction object, representing a EntityManagerHolder.
* Used as transaction object by JpaTransactionManager.
*/
private static class JpaTransactionObject extends JdbcTransactionObjectSupport {
private class JpaTransactionObject extends JdbcTransactionObjectSupport {
private EntityManagerHolder entityManagerHolder;
@ -606,6 +605,15 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager
return tx.getRollbackOnly();
}
public void flush() {
try {
this.entityManagerHolder.getEntityManager().flush();
}
catch (RuntimeException ex) {
throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
}
}
@Override
public Object createSavepoint() throws TransactionException {
return getSavepointManager().createSavepoint();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -18,7 +18,6 @@ package org.springframework.orm.hibernate3;
import java.util.ArrayList;
import java.util.List;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
@ -108,7 +107,7 @@ public class HibernateJtaTransactionTests extends TestCase {
session.createQuery("some query string");
sessionControl.setReturnValue(query, 1);
if (readOnly) {
session.setFlushMode(FlushMode.NEVER);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
}
query.list();
@ -370,6 +369,14 @@ public class HibernateJtaTransactionTests extends TestCase {
}
public void testJtaTransactionRollback() throws Exception {
doTestJtaTransactionRollback(false);
}
public void testJtaTransactionRollbackWithFlush() throws Exception {
doTestJtaTransactionRollback(true);
}
private void doTestJtaTransactionRollback(final boolean flush) throws Exception {
MockControl utControl = MockControl.createControl(UserTransaction.class);
UserTransaction ut = (UserTransaction) utControl.getMock();
ut.getStatus();
@ -390,6 +397,10 @@ public class HibernateJtaTransactionTests extends TestCase {
sfControl.setReturnValue(session, 1);
session.getSessionFactory();
sessionControl.setReturnValue(sf, 1);
if (flush) {
session.flush();
sessionControl.setVoidCallable(1);
}
sfControl.replay();
sessionControl.replay();
@ -409,6 +420,9 @@ public class HibernateJtaTransactionTests extends TestCase {
return l;
}
});
if (flush) {
status.flush();
}
status.setRollbackOnly();
sessionControl.verify();
sessionControl.reset();
@ -495,7 +509,7 @@ public class HibernateJtaTransactionTests extends TestCase {
sessionControl.setReturnValue(true, 5);
session.getFlushMode();
if (flushNever) {
sessionControl.setReturnValue(FlushMode.NEVER, 1);
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
if (!readOnly) {
session.setFlushMode(FlushMode.AUTO);
sessionControl.setVoidCallable(1);
@ -546,7 +560,7 @@ public class HibernateJtaTransactionTests extends TestCase {
session.flush();
sessionControl.setVoidCallable(1);
if (flushNever) {
session.setFlushMode(FlushMode.NEVER);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
}
}
@ -1064,15 +1078,15 @@ public class HibernateJtaTransactionTests extends TestCase {
session.getSessionFactory();
sessionControl.setReturnValue(sf, 1);
session.getFlushMode();
sessionControl.setReturnValue(FlushMode.NEVER, 1);
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
session.setFlushMode(FlushMode.AUTO);
sessionControl.setVoidCallable(1);
session.flush();
sessionControl.setVoidCallable(1);
session.setFlushMode(FlushMode.NEVER);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
session.getFlushMode();
sessionControl.setReturnValue(FlushMode.NEVER, 1);
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
@ -1306,7 +1320,7 @@ public class HibernateJtaTransactionTests extends TestCase {
sfControl.setReturnValue(tm, 7);
session.isOpen();
sessionControl.setReturnValue(true, 8);
session.setFlushMode(FlushMode.NEVER);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
session.close();
sessionControl.setReturnValue(null, 2);
@ -1667,7 +1681,7 @@ public class HibernateJtaTransactionTests extends TestCase {
sessionControl.setReturnValue(true, 5);
session.getFlushMode();
if (flushNever) {
sessionControl.setReturnValue(FlushMode.NEVER, 1);
sessionControl.setReturnValue(FlushMode.MANUAL, 1);
session.setFlushMode(FlushMode.AUTO);
sessionControl.setVoidCallable(1);
}
@ -1702,7 +1716,7 @@ public class HibernateJtaTransactionTests extends TestCase {
session.flush();
sessionControl.setVoidCallable(1);
if (flushNever) {
session.setFlushMode(FlushMode.NEVER);
session.setFlushMode(FlushMode.MANUAL);
sessionControl.setVoidCallable(1);
}
session.disconnect();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -1779,6 +1779,51 @@ public class HibernateTransactionManagerTests extends TestCase {
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
}
public void testTransactionFlush() throws Exception {
MockControl sfControl = MockControl.createControl(SessionFactory.class);
final SessionFactory sf = (SessionFactory) sfControl.getMock();
MockControl sessionControl = MockControl.createControl(Session.class);
final Session session = (Session) sessionControl.getMock();
MockControl txControl = MockControl.createControl(Transaction.class);
Transaction tx = (Transaction) txControl.getMock();
sf.openSession();
sfControl.setReturnValue(session, 1);
session.beginTransaction();
sessionControl.setReturnValue(tx, 1);
session.flush();
sessionControl.setVoidCallable(1);
tx.commit();
txControl.setVoidCallable(1);
session.close();
sessionControl.setReturnValue(null, 1);
sfControl.replay();
sessionControl.replay();
txControl.replay();
HibernateTransactionManager tm = new HibernateTransactionManager(sf);
tm.setPrepareConnection(false);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
status.flush();
}
});
assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
sfControl.verify();
sessionControl.verify();
txControl.verify();
}
protected void tearDown() {
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
assertFalse(TransactionSynchronizationManager.isSynchronizationActive());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -1222,4 +1222,41 @@ public class JdoTransactionManagerTests extends TestCase {
queryControl.verify();
}
public void testTransactionFlush() {
pmf.getConnectionFactory();
pmfControl.setReturnValue(null, 1);
pmf.getPersistenceManager();
pmfControl.setReturnValue(pm, 1);
pm.currentTransaction();
pmControl.setReturnValue(tx, 3);
pm.flush();
pmControl.setVoidCallable(1);
pm.close();
pmControl.setVoidCallable(1);
tx.begin();
txControl.setVoidCallable(1);
tx.getRollbackOnly();
txControl.setReturnValue(false, 1);
tx.commit();
txControl.setVoidCallable(1);
pmfControl.replay();
pmControl.replay();
txControl.replay();
PlatformTransactionManager tm = new JdoTransactionManager(pmf);
TransactionTemplate tt = new TransactionTemplate(tm);
assertTrue("Hasn't thread pm", !TransactionSynchronizationManager.hasResource(pmf));
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
assertTrue("Has thread pm", TransactionSynchronizationManager.hasResource(pmf));
status.flush();
}
});
assertTrue("Hasn't thread pm", !TransactionSynchronizationManager.hasResource(pmf));
assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -18,7 +18,6 @@ package org.springframework.orm.jpa;
import java.lang.reflect.Proxy;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.FlushModeType;
@ -95,9 +94,8 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
Person notThere = sharedEntityManager.getReference(Person.class, 666);
// We may get here (as with Hibernate).
// Either behaviour is
// valid--throw exception on first access
// or on getReference itself
// Either behaviour is valid: throw exception on first access
// or on getReference itself.
notThere.getFirstName();
}
@ -175,8 +173,7 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
}
protected void testInstantiateAndSave(EntityManager em) {
assertEquals("Should be no people from previous transactions",
0, countRowsInTable("person"));
assertEquals("Should be no people from previous transactions", 0, countRowsInTable("person"));
Person p = new Person();
p.setFirstName("Tony");
p.setLastName("Blair");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2006 the original author or authors.
* Copyright 2002-2009 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.
@ -93,13 +93,11 @@ public class ApplicationManagedEntityManagerIntegrationTests extends AbstractEnt
em.persist(p);
em.flush();
assertEquals("1 row must have been inserted",
1, countRowsInTable("person"));
assertEquals("1 row must have been inserted", 1, countRowsInTable("person"));
}
public void testStateClean() {
assertEquals("Should be no people from previous transactions",
0, countRowsInTable("person"));
assertEquals("Should be no people from previous transactions", 0, countRowsInTable("person"));
}
public void testReuseInNewTransaction() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2006 the original author or authors.
* Copyright 2002-2009 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.
@ -114,8 +114,7 @@ public class ContainerManagedEntityManagerIntegrationTests extends AbstractEntit
em.persist(p);
em.flush();
assertEquals("1 row must have been inserted",
1, countRowsInTable("person"));
assertEquals("1 row must have been inserted", 1, countRowsInTable("person"));
}
public void testReuseInNewTransaction() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -1071,4 +1071,33 @@ public class JpaTransactionManagerTests extends TestCase {
txControl.verify();
}
public void testTransactionFlush() {
managerControl.expectAndReturn(manager.getTransaction(), tx);
txControl.expectAndReturn(tx.getRollbackOnly(), false);
managerControl.expectAndReturn(manager.getTransaction(), tx);
tx.commit();
manager.flush();
factoryControl.replay();
managerControl.replay();
txControl.replay();
assertTrue(!TransactionSynchronizationManager.hasResource(factory));
assertTrue(!TransactionSynchronizationManager.isSynchronizationActive());
tt.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
assertTrue(TransactionSynchronizationManager.hasResource(factory));
status.flush();
}
});
assertTrue(!TransactionSynchronizationManager.hasResource(factory));
assertTrue(!TransactionSynchronizationManager.isSynchronizationActive());
factoryControl.verify();
managerControl.verify();
txControl.verify();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -335,15 +335,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (rollbackAnnotation != null) {
boolean rollbackOverride = rollbackAnnotation.value();
if (logger.isDebugEnabled()) {
logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" + rollback
+ "] for test context [" + testContext + "]");
logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" +
rollback + "] for test context [" + testContext + "]");
}
rollback = rollbackOverride;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("No method-level @Rollback override: using default rollback [" + rollback
+ "] for test context [" + testContext + "]");
logger.debug("No method-level @Rollback override: using default rollback [" +
rollback + "] for test context [" + testContext + "]");
}
}
return rollback;
@ -498,6 +498,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
public void endTransaction(boolean rollback) {
if (rollback) {
this.transactionStatus.flush();
this.transactionManager.rollback(this.transactionStatus);
}
else {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jca.cci.CannotGetCciConnectionException;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
@ -199,15 +198,16 @@ public abstract class ConnectionFactoryUtils {
* Callback for resource cleanup at the end of a non-native CCI transaction
* (e.g. when participating in a JTA transaction).
*/
private static class ConnectionSynchronization extends ResourceHolderSynchronization {
private static class ConnectionSynchronization
extends ResourceHolderSynchronization<ConnectionHolder, ConnectionFactory> {
public ConnectionSynchronization(ConnectionHolder connectionHolder, ConnectionFactory connectionFactory) {
super(connectionHolder, connectionFactory);
}
@Override
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
releaseConnection(((ConnectionHolder) resourceHolder).getConnection(), (ConnectionFactory) resourceKey);
protected void releaseResource(ConnectionHolder resourceHolder, ConnectionFactory resourceKey) {
releaseConnection(resourceHolder.getConnection(), resourceKey);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -75,6 +75,12 @@ public interface TransactionStatus extends SavepointManager {
*/
boolean isRollbackOnly();
/**
* Flush the underlying session to the datastore, if applicable:
* for example, all affected Hibernate/JPA sessions.
*/
void flush();
/**
* Return whether this transaction is completed, that is,
* whether it has already been committed or rolled back.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -22,6 +22,7 @@ import javax.transaction.UserTransaction;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
/**
* JTA transaction object, representing a {@link javax.transaction.UserTransaction}.
@ -72,4 +73,13 @@ public class JtaTransactionObject implements SmartTransactionObject {
}
}
/**
* This implementation triggers flush callbacks,
* assuming that they will flush all affected ORM sessions.
* @see org.springframework.transaction.support.TransactionSynchronization#flush()
*/
public void flush() {
TransactionSynchronizationUtils.triggerFlush();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -89,6 +89,12 @@ public abstract class AbstractTransactionStatus implements TransactionStatus {
return false;
}
/**
* This implementations is empty, considering flush as a no-op.
*/
public void flush() {
}
/**
* Mark this transaction as completed, that is, committed or rolled back.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -145,7 +145,7 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
/**
* Determine the rollback-only flag via checking both the transaction object,
* provided that the latter implements the SmartTransactionObject interface.
* provided that the latter implements the {@link SmartTransactionObject} interface.
* <p>Will return "true" if the transaction itself has been marked rollback-only
* by the transaction coordinator, for example in case of a timeout.
* @see SmartTransactionObject#isRollbackOnly
@ -156,6 +156,16 @@ public class DefaultTransactionStatus extends AbstractTransactionStatus {
((SmartTransactionObject) this.transaction).isRollbackOnly());
}
/**
* Delegate the flushing to the transaction object,
* provided that the latter implements the {@link SmartTransactionObject} interface.
*/
public void flush() {
if (this.transaction instanceof SmartTransactionObject) {
((SmartTransactionObject) this.transaction).flush();
}
}
/**
* This implementation exposes the SavepointManager interface
* of the underlying transaction object, if any.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -23,11 +23,12 @@ package org.springframework.transaction.support;
* @author Juergen Hoeller
* @since 2.5.5
*/
public class ResourceHolderSynchronization implements TransactionSynchronization {
public abstract class ResourceHolderSynchronization<H extends ResourceHolder, K>
implements TransactionSynchronization {
private final ResourceHolder resourceHolder;
private final H resourceHolder;
private final Object resourceKey;
private final K resourceKey;
private volatile boolean holderActive = true;
@ -38,7 +39,7 @@ public class ResourceHolderSynchronization implements TransactionSynchronization
* @param resourceKey the key to bind the ResourceHolder for
* @see TransactionSynchronizationManager#bindResource
*/
public ResourceHolderSynchronization(ResourceHolder resourceHolder, Object resourceKey) {
public ResourceHolderSynchronization(H resourceHolder, K resourceKey) {
this.resourceHolder = resourceHolder;
this.resourceKey = resourceKey;
}
@ -56,6 +57,10 @@ public class ResourceHolderSynchronization implements TransactionSynchronization
}
}
public void flush() {
flushResource(this.resourceHolder);
}
public void beforeCommit(boolean readOnly) {
}
@ -123,13 +128,20 @@ public class ResourceHolderSynchronization implements TransactionSynchronization
return true;
}
/**
* Flush callback for the given resource holder.
* @param resourceHolder the resource holder to flush
*/
protected void flushResource(H resourceHolder) {
}
/**
* After-commit callback for the given resource holder.
* Only called when the resource hasn't been released yet
* ({@link #shouldReleaseBeforeCompletion()}).
* @param resourceHolder the resource holder to process
*/
protected void processResourceAfterCommit(ResourceHolder resourceHolder) {
protected void processResourceAfterCommit(H resourceHolder) {
}
/**
@ -137,7 +149,7 @@ public class ResourceHolderSynchronization implements TransactionSynchronization
* @param resourceHolder the resource holder to process
* @param resourceKey the key that the ResourceHolder was bound for
*/
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
protected void releaseResource(H resourceHolder, K resourceKey) {
}
/**
@ -147,7 +159,7 @@ public class ResourceHolderSynchronization implements TransactionSynchronization
* @param committed whether the transaction has committed (<code>true</code>)
* or rolled back (<code>false</code>)
*/
protected void cleanupResource(ResourceHolder resourceHolder, Object resourceKey, boolean committed) {
protected void cleanupResource(H resourceHolder, K resourceKey, boolean committed) {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2006 the original author or authors.
* Copyright 2002-2009 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.
@ -41,7 +41,7 @@ public class SimpleTransactionStatus extends AbstractTransactionStatus {
/**
* Creates a new instance of the {@link SimpleTransactionStatus} class,
* Create a new instance of the {@link SimpleTransactionStatus} class,
* indicating a new transaction.
*/
public SimpleTransactionStatus() {
@ -49,7 +49,7 @@ public class SimpleTransactionStatus extends AbstractTransactionStatus {
}
/**
* Creates a new instance of the {@link SimpleTransactionStatus} class.
* Create a new instance of the {@link SimpleTransactionStatus} class.
* @param newTransaction whether to indicate a new transaction
*/
public SimpleTransactionStatus(boolean newTransaction) {
@ -58,7 +58,7 @@ public class SimpleTransactionStatus extends AbstractTransactionStatus {
public boolean isNewTransaction() {
return newTransaction;
return this.newTransaction;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2005 the original author or authors.
* Copyright 2002-2009 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,4 +39,10 @@ public interface SmartTransactionObject {
*/
boolean isRollbackOnly();
/**
* Flush the underlying sessions to the datastore, if applicable:
* for example, all affected Hibernate/JPA sessions.
*/
void flush();
}

View File

@ -1,12 +1,12 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,7 +27,7 @@ import org.springframework.transaction.TransactionStatus;
* @since 28.03.2003
* @see TransactionTemplate
*/
public abstract class TransactionCallbackWithoutResult implements TransactionCallback {
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
public final Object doInTransaction(TransactionStatus status) {
doInTransactionWithoutResult(status);
@ -35,10 +35,10 @@ public abstract class TransactionCallbackWithoutResult implements TransactionCal
}
/**
* Gets called by TransactionTemplate.execute within a transactional context.
* Does not need to care about transactions itself, although it can retrieve
* and influence the status of the current transaction via the given status
* object, e.g. setting rollback-only.
* Gets called by <code>TransactionTemplate.execute</code> within a transactional
* context. Does not need to care about transactions itself, although it can retrieve
* and influence the status of the current transaction via the given status object,
* e.g. setting rollback-only.
*
* <p>A RuntimeException thrown by the callback is treated as application
* exception that enforces a rollback. An exception gets propagated to the

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 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.
@ -60,6 +60,13 @@ public interface TransactionSynchronization {
*/
void resume();
/**
* Flush the underlying session to the datastore, if applicable:
* for example, a Hibernate/JPA session.
* @see org.springframework.transaction.TransactionStatus#flush()
*/
void flush();
/**
* Invoked before transaction commit (before "beforeCompletion").
* Can e.g. flush transactional O/R Mapping sessions to the database.

View File

@ -1,12 +1,12 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -42,6 +42,9 @@ public abstract class TransactionSynchronizationAdapter implements TransactionSy
public void resume() {
}
public void flush() {
}
public void beforeCommit(boolean readOnly) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 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.
@ -59,6 +59,17 @@ public abstract class TransactionSynchronizationUtils {
}
/**
* Trigger <code>flush</code> callbacks on all currently registered synchronizations.
* @throws RuntimeException if thrown by a <code>flush</code> callback
* @see TransactionSynchronization#flush()
*/
public static void triggerFlush() {
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
synchronization.flush();
}
}
/**
* Trigger <code>beforeCommit</code> callbacks on all currently registered synchronizations.
* @param readOnly whether the transaction is defined as read-only transaction
@ -121,7 +132,7 @@ public abstract class TransactionSynchronizationUtils {
* @see TransactionSynchronization#STATUS_UNKNOWN
*/
public static void triggerAfterCompletion(int completionStatus) {
List synchronizations = TransactionSynchronizationManager.getSynchronizations();
List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
invokeAfterCompletion(synchronizations, completionStatus);
}