Reputation: 389
My service should save data to a parent and child database tables, and rollback when an error ocurrs. I've tried forcing an error, using a hardcoded RuntimeException
, and found the transaction gets commited no matter what.
What I'm I missing?
I'm using Spring Boot 2, including the spring-boot-starter-jdbc
dependency.
Database is Oracle 11g.
Main configuration:
@SpringBootApplication
@EnableTransactionManagement
public class MyApplication extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Service layer:
@Service
public class MyBean {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final MyDAO myDao;
@Autowired
public MyBean (NamedParameterJdbcTemplate jdbcTemplate, MyDAO myDao) {
this.jdbcTemplate = jdbcTemplate;
this.myDao= myDao;
}
@Override
@Transactional
public void saveData(...){
myDao.saveData(jdbcTemplate, ...);
}
}
DAO:
public void saveData(jdbcTemplate, ...){
saveDataInParentDatatable(jdbcTemplate, ...);
saveDataInChildDatatable(jdbcTemplate, ...);
}
private void saveDataInChildDatatable(jdbcTemplate, ...){
throw new RuntimeException();
}
Upvotes: 6
Views: 7776
Reputation: 764
I faced a similar issue. I'm assuming that you were calling the MyBean.saveData method at MyBean's another method.
After searching, trying and failing a lot, I found this link: http://ignaciosuay.com/why-is-spring-ignoring-transactional/
In it, it's explained that when the invoked method is in the same class where is it invoked, the @Transactional annotation is ignored. The Spring explanation for it is:
“In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct.”
So I created another class to encapsulate my DAO method calling, used its method instead and it worked.
So for this case, it could be something like:
MyBean:
@Service
public class MyBean {
MyBean2 bean2;
public void saveData(...){
bean2.saveData(jdbcTemplate, ...);
}
}
MyBean2:
@Service
public class MyBean2 {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final MyDAO myDao;
@Autowired
public MyBean2 (NamedParameterJdbcTemplate jdbcTemplate, MyDAO myDao) {
this.jdbcTemplate = jdbcTemplate;
this.myDao= myDao;
}
@Override
@Transactional
public void saveData(...){
myDao.saveData(jdbcTemplate, ...);
}
}
Upvotes: 6
Reputation: 80
try this:
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
Upvotes: 2
Reputation: 19
recommended use:
@Transactional(rollbackFor = {Exception.class, RuntimeException.class})
Upvotes: 1