How to rollback transaction in spring - spring

I am facing a problem in transaction rollback using the #Transactional annotation.
I have the following methods in backingbean, service and dao classes:
public class ItemBackingBean {
public void saveUpdate() {
try {
ItemService.executeTransaction();
}
catch(Exception e) {
}
}
}
public class ItemService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void executeTransaction() {
deleteItem();
createOrder();
}
private void deleteItem() {
persist();
}
private void createOrder() {
persist();
}
private void persist() {
JpaDaoImpl.persist(object);
JpaDaoImpl.update(object);
}
}
public class JpaDaoImpl implements JpaDao {
#Transactional(readOnly = true)
public persist(Object object) {
getEm().persist(object);
}
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void update(Object object) {
getEm().merge(object);
}
#Transactional(readOnly = true)
public void remove(Object object) {
getEm().remove(object);
}
}
If any exception occurs in createOrder(), all transactions should rollback but it is not happening. Can any body tell me the problem?
What is the impact of #Transactional in JpaDaoImpl.java? The persist() and update() methods have different radOnly values. This Dao is existing code in our project and we don't want to change it. Can anybody help?
Regards,
Bandu

For those who don't want to throw exception (transaction should NOT be only rollbacked when exception happen), use this: TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

If any exception occurs in createOrder(), all transactions should rollback but it is not happening. Can any body tell me the problem?
Rollback occurs only for RuntimeExceptions (see http://docs.spring.io/spring/docs/2.0.8/reference/transaction.html "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;") but it is a customizable behaviour
You can keep the default transaction propagation that is PROPAGATION_REQUIRED without affecting the existing code if you want a ALL or NOTHING behaviour

Related

Why is a transaction full rollback?

The innerInvoke method is called in the outerInvoke method, and propagation = Propagation.REQUIRES_NEW is used in the innerInvoke method to prevent any problem in the transaction applied to the outerInvoke method even if there is a problem in the transaction applied to the innerInvoke method.
#Slf4j
#Service
#RequiredArgsConstructor
public class OuterService {
private final InnerService innerService;
private final FooRepository fooRepository;
#Transactional
public void outerInvoke() {
fooRepository.save(new Foo("foo"));
innerService.innerInvoke();
}
}
#Slf4j
#Service
#RequiredArgsConstructor
public class InnerService {
private final BarRepository barRepository;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerInvoke() {
try {
barRepository.save(new Bar("bar"));
} catch (Exception e) {
log.error("innerInvoke Exception : {}", e);
}
}
}
#SpringBootTest
class TransactionPropagationTestApplicationTests {
#Autowired
private OuterService outerService;
#Test
#Rollback(value = false)
void saveTest() {
outerService.outerInvoke();
}
}
However, if an IdentifierGenerationException, a subtype of DataAccessException, occurs in outerInvoke, all transactions are rolled back.
In my opinion, since the transactions are separated, the transaction of the outerInvoke method should be committed and the transaction of the innerInvoke method should be rolled back, so why are they all rolled back?

How to rollback transaction from service class?

I am trying to rollback for some condition by throwing exception. But i can not find proper way to do this. Here is my service class
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
And here is my BasicEM class
#Component
#Transactional(value = "transactionManager", rollbackFor = Exception.class)
public class BasicEM {
#PersistenceContext(unitName = "db1")
private EntityManager em;
public void update(Object target) {
em.merge(target);
}
}
So what i want is to call that update method, then if the condition is true undo the update.
My intention is that when i throw the exception, the transaction ends and does not commit the update. But i am wrong and the update is already done. Please help me to achieve my goal.
In order to achieve what you want you will need to have a transaction already in the Service method. The default propagation type for #Transactional(value = "transactionManager", rollbackFor = Exception.class) is Propagation.REQUIRED which means that if your Service is already included in a transaction, the basicEM.Update(entity) will also be included in such transaction.
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#Transactional(value = "transactionManager",
propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
If RollBackException is a RuntimeException you don't need to explicitly configure that the transaction should rollback when it is thrown. If it is not, then you need to configure it as follows: #Transactional(value = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class).

Rollback is not working for multiple insert

#Transactional annotation on a method inside the Application class is not rolling back insertions. However, #Transactional annotation on the service class(EmpService.java) method("insertEmp(Emp emp)") is working as expected.
Could someone please let me know why #Transactional working differently?
Spring Boot version - 2.1.3.RELEASE with the h2 database.
Please let me know if any additional information required.
#SpringBootApplication
#ComponentScan("org.saheb")
#EnableJpaRepositories("org.saheb.repo")
#EntityScan("org.saheb.vo")
#EnableTransactionManagement
public class SpringJpaTransactionApplication implements CommandLineRunner {
#Autowired
private EmpService empService;
public static void main(String[] args) {
SpringApplication.run(SpringJpaTransactionApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
insertSingleBatch();
}
#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)
public void insertSingleBatch() {
try {
Set<Emp> empSet = new LinkedHashSet<>();
Dept dept = new Dept();
dept.setDeptNo(10);
empSet.add(new Emp("abc", "abc", dept));
empSet.add(new Emp("xyz", "xyz", dept));
empSet.add(new Emp("def", "def", dept));
empSet.add(new Emp("pqrstu", "pqr", dept));// This will fail as max character allowed in 5 and should rollback all the insertion. But, first three records are getting saved in h2 database.
empService.insertEmp(empSet);
} catch (RuntimeException e) {
System.out.println("Exception in batch1.." + e.getMessage());
}
}
}
#Service
public class EmpService {
#Autowired
private EmpRepository empRepository;
//#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)//This is working as expected as all the insertions are rolling back after failure of 4th insertion
public void insertEmp(Set<Emp> empSet) {
System.out.println("Inside insert");
for (Emp temp : empSet) {
Emp temp2 =empRepository.save(temp);
System.out.println("inserted-->"+temp2.getFirstName());
}
}
}
You are "self-invocation" a #Transactional method from the same bean which will not work .This behaviour is well explained in the docs at here and here (Search the keyword "self-invocation")
You can simply move the #Transactional method to another bean.Then inject this bean to its client bean and invoke this #Transactional method.
Or use the TransactionTemplate to execute it within a transaction:
#Autowired
private TransactionTemplate txTemplate;
#Override
public void run(String... args) throws Exception {
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(status->{
insertSingleBatch();
return null;
});
}
Please note that TransactionTemplate will ignore the setting on the #Transactional and you have to configure it programatically.

#Transactional service method, two DAOs implemented from parameterized interface

I'm unable to rollback a transaction in Spring Boot 2.1.4 involving two DAOs (JDBC). The DAOs implement a parameterized interface. The first DAO inserts a record into three tables, and the last DAO inserts a record into one table. I'm forcing the last DAO insert to fail.
Database is DB2.
Some code:
public interface FooDao<T extends Foo> {
int insert(T foo) throws SQLException;
}
#Repository
public class FooDaoJdbc implements FooDao {
#Override
public int insert(Foo foo) throws SQLException {
insertFoo(foo);
insertFooDescriptions(foo);
insertFooActivity(foo);
return 0;
}
}
#Repository
public class BazDaoJdbc implements FooDao<Baz> {
#Override
public int insert(Baz baz) throws SQLException {
public interface FooService<T extends Foo> {
void add(T foo) throws SQLException;
}
#Service
public class BazServiceImpl implements FooService<Baz> {
private FooDao<Baz> bazDaoJdbc;
private FooDao<Foo> fooDaoJdbc;
#Transactional(rollbackFor = Exception.class)
public void add(Baz baz) throws SQLException {
fooDaoJdbc.insert(foo);
bazDaoJdbc.insert(baz);
}
}
It sure seems the FooDaoJdbc and BazDaoJdbc are in the same transaction when I debug TransactionAspectSupport, but the rollback seems to ignore FooDaoJdbc. I don't have a record in the "baz" table, but I would expect the three "foo" tables to rollback, too.
My eyes are crossed at this point. Do I have a misplaced annotation? Am I making things too "fancy" with generics? Thanks in advance for the help!
A #Transactional method only rollback when unchecked exception is thrown. So you have to throw RuntimeException in add method when an exception is thrown.
#Service
public class BazServiceImpl implements FooService<Baz> {
private FooDao<Baz> bazDaoJdbc;
private FooDao<Foo> fooDaoJdbc;
#Transactional
public void add(Baz baz) {
try {
fooDaoJdbc.insert(foo);
bazDaoJdbc.insert(baz);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
More information:
https://www.catalysts.cc/wissenswertes/spring-transactional-rollback-on-checked-exceptions/

Unittest a transactional method with REQUIRES_NEW and always force rollback

I want to test a service-method that inserts data into a table by calling a DAO in a loop. The service-method is annotated with
#Transactional(propagation = Propagation.REQUIRES_NEW)
The unit-test calls the service-method and is annotated with
#Transactional
Now I would like to tell the transaction that it always should do a rollback at the end. I don't want to clean up the db manually after the testruns.
#Rollback and EntityManager.getTransaction().setRollbackOnly() does'nt work. I think the reason is that the annotation and setRollbackOnly() are only applied on the Transaction that is created by the test-method and not on the transaction that is created by the service-method.
Does anyone know how to solve that?
I don't think it's possible to easily rollback a REQUIRES_NEW transaction. SpringTest starts a transaction and it can rollback the transaction that it started. But not the transactions started inside.
So you either may fall back to REQUIRED or write your tests to work fine even if they commit. If you choose the latter, you can achieve test isolation via randomization.
You can probably create a controllable mock implementation of org.springframework.transaction.PlatformTransactionManager delegating to the real one with only single difference that it would optionally mark a new transaction as read-only. Something like that if using java-based context configuration:
...
public class DaoTest {
...
#Autowired
Dao _dao;
#Autowired
MockPlatformTransactionManager _transactionManager;
...
#Test
#Transactional
public void testSomeAction() throws Exception
{
try (AutoCloseable ignored = _transactionManager.withRollBack())
{
_dao.someAction();
}
}
...
interface MockPlatformTransactionManager extends PlatformTransactionManager
{
AutoCloseable withRollBack();
}
...
// Somewhere in the #Configuration class
#Bean
MockPlatformTransactionManager transactionManager()
{
// an instance of real TM is adapted to become a MockPlatformTransactionManager
return new MockPlatformTransactionManager()
{
private final PlatformTransactionManager _delegate =
// TODO: same transaction manager as before
new DataSourceTransactionManager(dataSource());
private boolean shouldRollBack = false;
#Override
public TransactionStatus getTransaction(final TransactionDefinition definition)
throws TransactionException
{
final TransactionStatus transaction = _delegate.getTransaction(definition);
if (shouldRollBack)
transaction.setRollbackOnly();
return transaction;
}
#Override
public void commit(final TransactionStatus status) throws TransactionException
{
_delegate.commit(status);
}
#Override
public void rollback(final TransactionStatus status) throws TransactionException
{
_delegate.rollback(status);
}
#Override
public AutoCloseable withRollBack()
{
shouldRollBack = true;
return new AutoCloseable()
{
#Override
public void close() throws Exception
{
shouldRollBack = false;
}
};
}
};
}
If you use a PlatformTransactionManager implementation that permits to set the UserTransaction (like org.springframework.transaction.jta.JtaTransactionManager) you can instantiate an UserTransaction implementation which commit method does a rollback.
import com.atomikos.icatch.jta.UserTransactionImp;
#Bean
public UserTransaction myUserTransaction() {
final UserTransactionImp userTransactionImp = new UserTransactionImp() {
#Override
public void commit() throws javax.transaction.RollbackException, javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException, javax.transaction.SystemException, java.lang.IllegalStateException,
java.lang.SecurityException {
rollback();
}
};
return userTransactionImp;
}
And then, in your PlatformTransactionManager bean:
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier("myUserTransaction") UserTransaction myUserTransaction,
#Qualifier("myTransactionManager") TransactionManager myTransactionManager
) {
final JtaTransactionManager jtaTm = new JtaTransactionManager();
jtaTm.setTransactionManager(myTransactionManager);
jtaTm.setUserTransaction(myUserTransaction);
return jtaTm;
}
I've also made my dao does not make any flush.

Resources