How to do Rollback with Transactional Annotation - java

I am trying to do Transactional Rollback in my methods. Intentionally i am making the insert fails to find out . But i don't see its getting rolled back . Please help what i am missing.
#Service
public class ModesService implements IModesService{
ChargeRuleDao chargeRuleDao;
public ModesService(ChargeRuleDao chargeRuleDao){
this.chargeRuleDao = chargeRuleDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRED)
public void process(ChargeRule chargeRule){
chargeRuleDao.deleteShippingChargeAttr(shippingChargeRuleID);
chargeRuleDao.deleteShippingCharge(shippingChargeRuleID);
chargeRuleDao.deleteShippingChargeDest(shippingChargeRuleID);
//Delete
chargeRuleDao.insertShipChargeFeedRule(chargeRule);
}
In DAOImpl class i have methods like below for all deletions and insertion.
#Override
public int deleteShippingChargeAttr(String test) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("ABC" "ABC", Types.VARCHAR);
return jdbcTemplate.update(DELETE_QUERY, params);
}

You may try #Transactional(rollbackFor = XYZException.class).
XYZException should be an exception which should wrap all the exceptions/exception for which you want to rollback the transaction.

Rollback occurs by default for every unchecked exception. That means you need to throw some type unchecked exception, like for example
throw new NullPointerException();
in your insertShipChargeFeedRule(chargeRule);
more about #Transactional here https://javamondays.com/spring-transactions-explained/

Related

Rollback a transaction when a query throws an exception

folks,
The method below is part of a transaction using JPA. I'm trying to discover how to add a functionality in this method so when the query within the method throws an exception, all the transaction rolls back.
What could I do in this case? Below is the code:
#Transactional
public void deleteDadosSExecReenvCancelada(Long nuSeqConsecao){
try{
StringBuilder query = new StringBuilder();
query.append(" DELETE FROM sigpc_fnde.s_execucao_financ_gru_reenv where NU_SEQ_EXEC_FINANC_REENV in (SELECT NU_SEQ_EXECUCAO_FINANCEIRA FROM sigpc_fnde.s_exec_financ_reenv where NU_SEQ_CONCESSAO = ?)");
getEntityManager().createNativeQuery(query.toString()).setParameter(1, nuSeqConsecao).executeUpdate() ;
StringBuilder sql = new StringBuilder();
sql.append(" DELETE FROM sigpc_fnde.s_exec_financ_reenv where NU_SEQ_CONCESSAO = ?");
getEntityManager().createNativeQuery(sql.toString()).setParameter(1, nuSeqConsecao).executeUpdate() ;
} catch (Exception e){
e.getCause();
}
}
I would like to know if simply the Annotation #Transactional(rollbackFor=true) guarantees this condition to happen.
#Transactional will detect any unchecked exception thrown in your method and your transaction will be marked as rollback only. I think #Transactional(rollbackFor = Exception.class) is not necessary in your case since all possible exceptions that could be thrown are RuntimeException.
Also, your EntityManager needs to be injected by Spring. If it's not the case, let Spring inject the EntityManager.
An example on how you could let Spring inject the bean:
#RequiredArgsConstructor
class MyService {
...
#PersistenceContext
private final EntityManager entityManager;
...
}

#Transactional rollbackFor scope

I'm using #Transactional in my code and I'm created a custom exception to show error messages in specific format in UI.
public class MyCustomException extends RuntimeException
When this exception is encountered I still want to rollback my transactions, same as in case when any other exception occurs.
So to make it work, I writing below code:
// service method called from rest controller
public List<String> getMyData() {
List<String> errors = new ArrayList();
try {
businessMethod();
} catch (MyCustomException e) {
log.error(e.getMessage);
errors.add(e.getMessage)
}
return errors.
}
#Transactional(rollbackFor = {MyCustomException.class, RuntimeException.class, Exception.class})
public String businessMethod() {
// Business logic to get data that can throw MyCustomException
}
My questions are:
If I'm mentioning MyCustomException.class in rollbackFor, do I need to also mention RuntimeException.class, Exception.class. Or whatever is mentioned in rollbackFor gets appended along with default exceptions for which transaction is rolled-back.
Although I'm escaping the MyCustomException from businessMethod(), but I'm catching it on its calling method getMyData(). I'm assuming that the transaction will be rolled-back in case of exception, correct?
The transaction will be rolled back on any RuntimeException, so it is not necessary to declare your own MyException.class in rollbackFor section, since your MyException extends RuntimeException. If you declare Exception.class the rollback will be performed on any Exception. But in your case you do not need rollbackFor at all.
Yes, it is correct. Your transcation starts and ends in businessMethod().

UnexpectedRollBackException in Spring inner transaction

I have two classes:
#Service
#Transaction
class A {
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
#Service
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void method2(){
save2()
throw new SqlException();
}
}
I got an SqlException as expected, but also an UnexpectedRollBackException, and the program stops.
I want to know why the data persisted by save2() is not rolled back?
Is it a problem with outer transaction?
UPDATE: I tried catching UnexpectedRollBackException in class A and everything works fine. But I still need some kind of explanation why I get the exception? I suppose the outer transaction should be suspended when the inner transaction begins, so why the rollback is unexpected for the outer transaction?
Thanks.
First of all: you are not injecting the instance of B into class A via spring. i.e. your b is not managed by spring => this leads to the behaviour: Spring is ignoring the #Transactional annotation on method.
Second if you don't want to rollback on SqlException you have to specify noRollbackFor=SqlException.class
UPDATE: after clarification, the call is happening on managed bean. But the problem that expected behaviour - transaction inside of transaction is not supported in general by the transaction management system out of the box. https://docs.spring.io/spring/docs/4.3.11.RELEASE/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRES_NEW
Unless the details on that system are provided it is impossible to step forward. Out of content the NESTED transaction propagation could be better, then REQUIRES_NEW, because it is making a rollback point for the external transaction and starts new one.
I have a simliar scenario like this and resolved it using propagation = Propagation.NESTED from where the second method is calling. rewrite the code as mentioned below and try.
#Service
class A {
#Transactional(rollbackFor = { HibernateException.class}, propagation = Propagation.NESTED)
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
Note: Transactional is used in method level for class A with propagation nested . Class B shown below.
#Service
class B {
#Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRED)
public void method2(){
save2()
throw new SqlException();
}
}
This has resolved the issue in my case.

Spring : Caught EmptyResultDataAccessException causing entire transaction to rollback

I have two methods are shown below.
#Transactional
public void methodA(){
logger.trace("Executing methodA");
methodB()
logger.trace("Executing methodA completed");
}
public void methodB(){
//other codes here
try{
staffDao.queryById(1) //Fetch a record from database
}catch(EmptyResultDataAccessException e){
logger.trace("Staff does not exists")
}
//other codes here
}
When there occurs an EmptyResultDataAccessException within methodB()
, the entire transaction started on methodA() is rollbacked, by below exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
I know this is the default behaviour of spring #Transactional annotation.
For my case, I need to commit the transaction even when there is an EmptyResultDataAccessException. As EmptyResultDataAccessException is a RuntimeException, I can't use the noRollBackFor attribute of #Transactional annotation.
Can anyone suggest a solution ?
I have not looked closely at your code yet but if you just need a way to not rollback the transaction for a particular exception, you can mark that in #Transactional annotation.
#Transactional(noRollbackFor = {EmptyResultDataAccessException.class})
public void methodA(){
.
.
}
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
Looking at the code of deleteById in SimpleJpaRepository
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
I thought this is just a convenience method. If I don't want the exception I just implement it the way I need it:
repo.findById(id).ifPresent(repo::delete);

Java Spring #Transactional method not rolling back as expected

Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.
I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)
Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}

Categories