ChatGPT解决这个技术问题 Extra ChatGPT

Annotation @Transactional. How to rollback?

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!


T
Tomasz Nurkiewicz

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.


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.
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.
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.
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?
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.
S
Stefan K.

or programatically

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Yes. Because sometimes you need to rollback without throwing an error.
You are a lifesaver.
This is a more appropriate way to invoke a rollback imo, ideally you don't want to throw exceptions for known or controlled conditions.
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.
which transaction do you want to roll back in your "non-transactional repository"?
A
Alex Barnes

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.


R
Roberto Rodriguez

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 :-)


no it doesn't help at all TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); below you helped