Annotation @Transactional. How to rollback?
Asked Answered
H

5

117

I used this annotation successfully for a Dao class. And rollback works for tests.

But now I need to rollback real code, not just tests. There are special annotations for use in tests. But which annotations are for non-test code? It is a big question for me. I spent a day for that already. The official documentation did not meet my needs.

class MyClass { // this does not make rollback! And record appears in DB.
        EmployeeDaoInterface employeeDao;

        public MyClass() {
            ApplicationContext context = new ClassPathXmlApplicationContext(
                    new String[] { "HibernateDaoBeans.xml" });
            employeeDao = (IEmployeeDao) context.getBean("employeeDao");
         }

        @Transactional(rollbackFor={Exception.class})
    public void doInsert( Employee newEmp ) throws Exception {
        employeeDao.insertEmployee(newEmp);
        throw new RuntimeException();
    }
}

employeeDao is

@Transactional
public class EmployeeDao implements IEmployeeDao {
    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public void insertEmployee(Employee emp) {
        sessionFactory.getCurrentSession().save(emp);
    }
}

And here is a test for which the annotations work well:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/HibernateDaoBeans.xml" })
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
@Transactional
public class EmployeeDaoTest {

    @Autowired
    EmployeeDaoInterface empDao;

    @Test
    public void insert_record() {
       ...
       assertTrue(empDao.insertEmployee(newEmp));
    }

HibernateDaoBeans.xml

   ...
<bean id="employeeDao" class="Hibernate.EmployeeDao">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
    <tx:annotation-driven transaction-manager="txManager"/>

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
   ...



**YES, I rolled back the transaction. I just added BEAN for the service... and then annotation @Transactional begin to work :-) **

<bean id="service" class="main.MyClass">
    <property name="employeeDao" ref="employeeDao" />
</bean>

Thanks all, Russia will not forget you!

Hedron answered 24/10, 2011 at 8:8 Comment(0)
H
191

Just throw any RuntimeException from a method marked as @Transactional.

By default all RuntimeExceptions rollback transaction whereas checked exceptions don't. This is an EJB legacy. You can configure this by using rollbackFor() and noRollbackFor() annotation parameters:

@Transactional(rollbackFor=Exception.class)

This will rollback transaction after throwing any exception.

Herodotus answered 24/10, 2011 at 8:13 Comment(15)
I tried it. It does not work! I tell about Rollback operation not directly in Dao object, that may be will work. I tell about another object that use sequence of call of Dao methoda that use @Transactional. And I try to add the same annotation for my class that call this Dao. But it does not work here.Hedron
It really works this way :-). If you have a service calling several DAOs, the service needs to have a @Transactional annotation as well. Otherwise each DAO call start and commits new transaction before you throw an exception in the service. The bottom line is: the exception must leave (escape) a method marked as @Transactional.Herodotus
Look at added code in first post. There are part of code I have. And after execution I have the record in DB. => rollback does not work yet.Hedron
Does throwing an exception from DAO not rolling back the transaction as well? Do you have org.springframework.orm.hibernate3.HibernateTransactionManager configured in your Spring context? If you enable org.springframework.transaction logger, does it show anything interesting?Herodotus
Tomasz Nurkiewicz, you said "The bottom line is: the exception must leave (escape) a method marked as @Transactional". You said it about my code and my boottom line? I call method dao which is under @transactional. And I guess it commit Transaction. And my "throw new Exceprion" is late! But I need make rollback in Service method. Not directly in Dao method as I guess you advice me.Hedron
Tomasz, no, when I throw "throw new RuntimeException();" right in Dao method after "sessionFactory.getCurrentSession().save(emp);" is make rollback. But I tell about outside call!!! The situation that showed in first post does not rollback.Hedron
And what if you remove @Transactional from DAOs and leave them only in service layer (some consider this to be a better approach)? Your situation is weird - by default nested transaction in DAO should join already existing transaction started in service layer. This means that leaving DAO should not commit/rollback the transaction yet - it should happen when leaving service layer. Can you add your txManager configuration? Also please examine org.springframework.transaction logger.Herodotus
Look at the end of first post. There are several lines of txManager. I can add that right after calling of dao methos the commit happens. And "throw" are after commit. Now I try to delete @Transactional from Dao and leave it only in Service. But it is not solution even if it will workHedron
no, it does not work. Badness:No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one hereHedron
OK, I think I got it. Service class is not transactional, even though we think it is. Call org.springframework.transaction.support. TransactionSynchronizationManager. isActualTransactionActive() both in service and in DAO. It will return false for the former call and true for the latter. Please extract IMyClass interface with doInsert() method. Now both calls will return true and the code should work. Of course you must now use IMyClass everywhere.Herodotus
Tomasz, look at append of first post, I wrote there solution. I made bean of "MyClass" and get it from apropriate place... and then transactional will work. And your method showed "true". Now I tried to make interface as you said. I remember that in my DAO beans(!!!) it was solution. Because before I made interface it did not work. Therefore making interface may be really better solution of this problem.Hedron
Tomasz, no man, I added interface, but it does not work. I hope @transactional annotation works only when we use spring beans. I agree to use spring beans in this case. It is not big problem.Hedron
Yes, that is understandable, I was 100% sure that MyClass is a Spring bean and didn't even bother to ask (my bad). Otherwise Spring has no idea about your class and its annotations. Glad you solved your issue, but note that my suggestions about interface and propagating exceptions are still relevant.Herodotus
I asked many question already, but I have one more... I think it is last one. Do you offer to extract interface for service. But will this interface substitute bean? If it will not, then tell me reason to do that. Is it good practice or it is substitution for bean? I added interface already, but when I deleted record about bean in xml-file it was not work. So that it did not substitute bean. So, what reason to do that can you report.Hedron
If you are using Spring Boot and let it auto-configure the DataSource, it will use HikariDataSource which has the auto-commit set to true by default. You need to set it to false: hikariDataSource.setAutoCommit(false); I made this change in the @Configuration class when configuring the DataSourceTransactionManager bean.Vellavelleity
L
118

or programatically

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Lisandra answered 21/1, 2016 at 20:15 Comment(4)
Yes. Because sometimes you need to rollback without throwing an error.Schaffhausen
This is a more appropriate way to invoke a rollback imo, ideally you don't want to throw exceptions for known or controlled conditions.Crosspurpose
Sometimes it doesn't work. For example, in my case I have a transactional service and non-transactional repository. I call repository from service, so, it should participate in a transaction opened by the service. But if I call your code from my non-transactional repository, it throws NoTransactionException despite the fact that transaction exists.Astronaut
which transaction do you want to roll back in your "non-transactional repository"?Lisandra
I
5

You can throw an unchecked exception from the method which you wish to roll back. This will be detected by spring and your transaction will be marked as rollback only.

I'm assuming you're using Spring here. And I assume the annotations you refer to in your tests are the spring test based annotations.

The recommended way to indicate to the Spring Framework's transaction infrastructure that a transaction's work is to be rolled back is to throw an Exception from code that is currently executing in the context of a transaction.

and note that:

please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException.

Intercolumniation answered 24/10, 2011 at 8:14 Comment(0)
F
2

For me rollbackFor was not enough, so I had to put this and it works as expected:

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)

I hope it helps :-)

Flaunt answered 13/12, 2017 at 16:58 Comment(2)
no it doesn't help at all TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); below you helpedBorak
Now above, not below :DVest
C
0

According to the spring framework documentation:

In its default configuration, the Spring Framework’s transaction infrastructure code marks a transaction for rollback only in the case of runtime, unchecked exceptions.

So in order to mark a transaction for rollback, make sure you throw an unchecked runtime exception.

Cleome answered 23/4 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.