Spring @Transaction does not rollback on Exception thrown

26,794

Solution 1

I found the cause of my problem and why the transaction (seemingly) not managed properly.

Somewhere in my code

/**
 * {@inheritDoc}
 */
@Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRED)
public void doWork(Parameters param) throws ServiceException 
{
    ... do bunch of stuff ..
    myDao1.persist(entity)

    -- Some logic here --

    ... do bunch of stuff ..
    myDao2.persist(entity2)

    if(true)
        throw new ServiceException("random error")
}

The part where it says "-- Some logic here --", there was some logic done that uses raw SQL and call on execute update:

Query query = sessionFactory.getCurrentSession().createSQLQuery(queryText);
query.executeUpdate();

And because it's not using Hibernate query, and instead using raw SQL execution, it caused a flush to be called and thus any work done prior to the call will be committed along with it.

I re-work the flow of logic to account for this logic to make sure transaction is managed properly. While using raw SQL might be an indication that there's something wrong - it was something necessary to be done due to the things that the service try to accomplish and to improve the performance of the service.

Solution 2

That is the intended behavior of transaction management. The default behavior for @Transactional is to rollback only for runtime exceptions. If you want your stuff to rollback after throwing DaoException then add it to the rollback exception list. Don't forget to also include RuntimeException also. Try the following on the Dao class @Transactional(propagation=Propagation.Mandatory, rollbackFor={RuntimeException.class, DaoException.class})

Solution 3

Try remove the @Transactional annotation from the DaoImpl class. I suspect what might be happening is that the transaction is being committed when it crosses back over that transaction boundary (DaoImpl). I've had mixed success with this setup. You can try some different transaction approaches to the "inner" transaction.

The other thing you can do is turn on spring transaction logging. It think its category org.springframework.transaction or something. That way you will see exactly what it is doing w.r.t to roll back and commit of transactions...

Share:
26,794
TS-
Author by

TS-

Best to contact me through LinkedIn: http://ca.linkedin.com/in/theodoresurjo Seasoned technology professional with solid development background and strong leadership skills. I have extensive experience in solution architecture and implementation of complex systems, grown and led development teams, and been involved in various client-facing roles. The recommendations I received speak volumes of my track record as a developer and leader; having graduated with Distinction from the University of Waterloo is a proof of my academic excellence. I enjoy challenging roles and continuously look for professional growth.

Updated on March 01, 2020

Comments

  • TS-
    TS- about 4 years

    I've searched around for this question, there's quite a few of them here on StackOverflow and Google but I can't seem to get anything working for me.

    here are my codes Spring config: (I dont use any pointcut - I think I dont need to?)

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    ...
    </bean>
    
    <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
     <property name="dataSource" ref="dataSource" />
     ...
    </bean>
    
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
            <property name="sessionFactory" ref="hibernateSessionFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    

    I have a Service class:

    @Service
    public class ServiceImpl implements ServiceInterface 
    {
        /**
         * Injected session factory
         */
        @Autowired(required=true)
        private SessionFactory sessionFactory;
    
        @Autowired(required=true)
        private Dao myDao;
    
        /**
         * {@inheritDoc}
         */
        @Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRED)
        public void scheduleBlast(BlastParameters blastParameters) throws ServiceException 
        {
            ... do bunch of stuff ..
            myDao.persist(entity)
    
            if(true)
                throw new ServiceException("random error")
        }
    
        .. setter methods and other stuff ..
    }
    

    and a Dao class:

    public class DaoImpl implements DaoInterface
    {
        @Autowired(required=true)
        private SessionFactory sessionFactory
    
        /**
         * {@inheritDoc}
         */
        @Transactional(propagation=Propagation.MANDATORY)
        public void persist(Entity e) throws DaoException
        {
            try
            {
                sessionFactory.getCurrentSession().persist(e);
            }
            catch(Exception ex)
            {
                throw new DaoException(ex);
            }
        }
    
    
        .. setter methods and other stuff ..
    }
    

    Some unnecessary details are eliminated (eg. missing setter, etc), assume that code works perfectly fine.

    My problem with the above is that when I added the throw random exception line, it does not rollback, the object being persisted through the DAO stays in the db.

    I am using Spring 3.1 and Hibernate 3.6 (because there was a bug with Hibernate 4.0 on Spring 3.1)

    Thoughts?

    Thank you