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/
Related
#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.
I have the following scenario.
I have one transaction method which calls another transaction method which having REQUIRED_NEW Propagation. if the first method gets exception then the second method (REQUIRED_NEW Propagation) also rollbacks.
I am using JPA, Spring-boot and chainedKakfkaTransactionManager
I have tried with changing chainedKakfkaTransactionManager to default one still no luck
here is my code :
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}
Rollback is happening in the handleError method too. is there any code mistake?
Thanks #DarrenForsythe,
By Creating an autowire object for the same class (self-reference) its worked for me
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
// CREATE SELF REFRENCE
#Autowired
private ComponentService componentService;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
componentService.handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}
I have a very simple code comprising of Service -> RequestProcessor -> DAO having 2-3 classes (interface, abstract, concrete) in each layer.
Service layer:-
public interface Service {
public void saveOrUpdate(Object entity, String operationName);
}
}
public abstract class AbstractService implements Service{
public abstract ReqProcessor getRP();
#Override
public void saveOrUpdate(Object entity, String operationName) {
ReqProcessor hiberTestRP = getRP();
hiberTestRP.saveOrUpdate(entity, operationName);
}
}
#Component
public class ServiceImpl extends AbstractService {
#Autowired
public ReqProcessor hibertestRPImpl;
#Override
public HiberTestRP getRP() {
return hibertestRPImpl;
}
}
ReqProcessor layer:-
public interface ReqProcessor {
public void saveOrUpdate(Object entity, String operationName);
public void saveObject();
}
}
public abstract class AbstractReqProcessor implements ReqProcessor {
#Override
public void saveOrUpdate(Object entity, String operationName) {
saveObject();
}
}
#Component
public class ReqProcessorImpl extends AbstractReqProcessor {
#Autowired
public CustomHibernateDao customWSDaoImpl;
#Override
#Transactional(value="transactionManagerWS", propagation=Propagation.REQUIRED)
public void saveObject() {
// object created //
customWSDaoImpl.saveOrUpdate(object); // exception is thrown at this line
}
}
DAO layer:-
public interface CustomHibernateDao {
public void saveOrUpdate(Object entity, String operationName);
}
#Repository
#Transactional(value="transactionManagerWS", propagation=Propagation.MANDATORY)
public class CustomWSDaoImpl implements CustomHibernateDao {
#Autowired
public SessionFactory sessionFactoryWS;
protected Session getCurrentSession() {
return sessionFactoryWS.getCurrentSession();
}
#Override
public void saveOrUpdate(Object entity, String operationName) {
Session session = getCurrentSession();
session.saveOrUpdate(entity);
}
}
I get the following exception at the commented line :
Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:359)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:447)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:277)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy37.saveOrUpdate(Unknown Source)
The code works absolutely fine when the abstract classes are removed, with only interfaces and their implementing classes remaining. But with the above setup, the transaction is not being propagated from ReqProcessor layer to the DAO layer. Please help. (Dont mind the 'public' accessors everywhere, it's just for testing)
I have also searched on SO and other forums but couldnt find a solution.
As #m-deinum has mentioned, Spring uses proxies to add "transactional" functionality, and this feature does not work when you call method annotated with #Transactional from another method of the class.
You have two ways to fix the problem:
In AbstractReqProcessor autowire ApplicationContext and then use it to get a bean of CustomHibernateDao type. On this retrieved object you can call saveObject - then the transactional magic happens.
The more preferred way is to annotate method saveOrUpdate of class AbstractService with #Transactional annotation too - then it will work again.
But I think you know the cause of the problem now and you can find another - more suitable for you - way.
I have following simple service:
#Service
public class TestServiceImpl implements TestService {
#Override
public void countExternal(Integer arg1) {
System.out.println("test - lock external");
count(arg1, new Integer(1));
}
public void count(Integer arg1, Integer arg2) {
System.out.println("test - lock internal");
}
}
that implements my simple interface:
public interface TestService {
void countExternal(Integer arg1);
}
Here's the aspect that I am using to do some validation etc. during count method:
#Aspect
#Component
public class TestAdvicer {
#Around("execution(* count(..))")
public Object advice(ProceedingJoinPoint joinPoint) throws Throwable {
// do som magic here
return joinPoint.proceed();
}
}
In my Spring configuration I have included autoproxying:
#EnableAspectJAutoProxy(proxyTargetClass = true)
Unfortunately, my TestAdvicer is never executed since count method is invoked from countExternal method. count method is executed on Proxy object and because of that advice didn't run.
Do you know how can I run my advice on Proxy object? What is the best way to solve this problem?
I'm trying to execute sample of transaction code. I throw checked exception from transactional method and set rollbackFor parameter for the class of this exception. However this transaction doesn't rollback. So my question is what may be the reason of that behaviour. Here is a sample of my code:
MyService:
#Service
#Transactional
#EnableTransactionManagement
public class MyService implements IMyService {
#Autowired
private IMyDao myDao;
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = BreakException.class)
public void test() throws BreakException {
myDao.updateMethodA("64", "'N'", "'2015.01.01/0008661/0008'");
if(true) throw new BreakException();
myDao.updateMethodB("15", "'N'", "'2015.01.01/0008661/0008'");
}
}
MyDao:
#Repository
public class MyDao implements IMyDao {
#Override
#Transactional(propagation=Propagation.MANDATORY)
public void updateOperationA(String x, String y, String z) {
String sql = "UPDATE ...."; //masked for example
jdbcTemplate.update(sql);
}
#Override
#Transactional(propagation=Propagation.MANDATORY, rollbackFor = Exception.class)
public void updateOperationB(String saldoPo, String saldo_status, String unikalne_id) throws BreakException {
String sql = "UPDATE ...."; //masked for example
//if(true) throw new BreakException();
jdbcTemplate.update(sql);
}
}
1.) So when I call test() on MyService the update from method updateMethodA is not rolledback after BreakException() is thrown.
2.) BreakException is my class that extends Exception
3.) When I throw BreakException from updateOperationB(commented in example) it does rollback correctly.
4.) When I change BreakException to extend TransactionException it works as expected so it seems the problem is only when I extend Exception.