Currently if exception occurs in method C, code is not rolling back database changes from method B. My expectation is method A should manage transaction in such a way that if exception occurs in method C, code should rollback changes done in method B.
I am using spring boot, maven project.
class SomeClassA{
#Autowired
SomeClassB someClassB;
#Autowired
SomeClassC someClassC;
#Transactional
public A(){
try{
//This method works fine with some database operations.
someClassB.B();
//In this method, exception occurrs.
someClassC.C();
}
catch(Exception e){
}
}
}
class SomeClassB{
#Transactional
public B(){
//some code with database operation
}
}
class SomeClassC{
#Transactional
public C(){
//some code with database operation
//some exception occurs here
}
}
Is it a Checked or Runtime exception ?
Because the default behaviour of #Transactional says:
Any RuntimeException triggers rollback, and any checked Exception does
not.
Related
I'm trying to do an Spring Boot integration test where I call a method of a service which is composed by other two methods methods (all are #Transactional). One of them save a record in ddbb and the other throws a RuntimeException (where, in running production app cause a Rollback of the saved record.
I want to test this rollback behaviour in the test (for example that if the methods don't have #Transactional the rollback is not done). Currently if the #Rollback(true) the rollback is done even if the methods don't have the #Transactional and if the #Rollback(false) any rollback is done. The objective is to test that the rollback is done caused by the transaction reaction to the runtime exception and not because the test automatically rollback.
#ExtendWith(SpringExtension.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration
class RefundServiceIT {
#Test
#Rollback(false)
void testTransactions() {
Assertions.assertThrows(RuntimeException.class, () -> {
refundService.refund();
});
//assertions rollback caused by runtime is done
}
}
#Service
public class refundService {
#Transactional
public refund() {
saveddbb();
wrong();
}
#Transactional
public saveddbb() {
//save an element in ddbb
}
#Transactional
public wrong() {
//throw new RuntimeException()
}
}
Thank you very much!
Hello I am developping a back-end using Spring boot and MongoDB 4.0. In order to add transactions I have implemented the MongoTransactionManager as seen in the documentations spring mongo transactions
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
But when I annotate a method with #Transactional(rollbackFor = NullPointerException.class) it does not roll back for this exception.
For example the following test does not work.
Do you have any advices to fix this issue please ?
#Test
#Transactional(rollbackFor = NullPointerException.class)
public void testTransaction() {
try {
myRepo.deleteAll();
throw new NullPointerException();
} catch (
NullPointerException e) {
} finally {
assertThat(myRepo.findAll()).isNotEmpty();
}
}
Just figure it out that MongoTransactionManager does not work if you also register a Bean MongoTemplate.
Moreover, surprinsingly, #Transactional method does not work if it is a #Test method. You must extract the #Transactional method in a #Service.
Because you just caught your NPE and didn't do anything with it. For transaction being rollbacked your method should throw NPE out of the method.
We are using Spring 4.x and Spring Data JPA with declarative transaction management, I have a Controller, Service and a Repository like below pseudo code.
#Service
#Transactional(readOnly=true)
public class SampleService {
#Autowired
private SampleRepository sampleRepository;
#Transactional
public MyEntity saveMyEntity(MyEntity entity) {
//do some business logic
return sampleRepository.save(entity);
}
}
public class SampleController {
#Autowired
private SampleService sampleService;
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
}
In the above code an error gets thrown after call to sampleService.saveMyEntity(entity); but the transaction doesn't mark for rollback, so end user will get an error page but behind the scene entity got persisted.
Is there any way i can rollback the transaction ?
You can do the following.
#Transactional(rollbackFor=Exception.class)
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
Since the default Transaction Propagation is Required not Required new. Transaction will actually begin at SampleController.saveSample() and the same one will be used SampleService.saveMyEntity(). When an exception thrown from saveSample() the entire transaction will be rolled back.
I have:
1) Service:
#Service("scanner")
#Transactional
public class Scanner
{
#Inject
AnalyzerService analyzerService;
#Transactional
private void scan() {
analyzerService.analyze();
}
}
2) Service:
#Service
public class AnalyzerService
{
#Inject
AnalyzerDao analyzerDao;
#Transactional
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
save(items);
}
#Transactional
private void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
3) Dao:
#Repository
public class AnalyzerDao extends GenericDaoImpl<AnalyzerResult>
{
//all needed methods for find, edit, delete and save which works fine in other cases.
}
Question:
Why after call analzyerDao.save(items) DB is still empty? Is it problem with transaction some how?
When I invoke flush() method and getSession().getTransaction().commit() just after line analyzerDao.save(items) then records appearing in DB but exception is thrown:
Caused by: org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction; nested exception is org.hibernate.TransactionException: Transaction not successfully started
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:660)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy44.execute(Unknown Source)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
... 1 more
Caused by: org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:127)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
... 9 more
How should implementation be done to work 'save' method fine?
What should I do to save items just in line analyzerDao.save(items) and not only after first transaction will be finished?
What you need is a new transaction just to save what you need to save. You can achieve this by configuring the propagation of #Transactional annotation to REQUIRES_NEW.
Unfortunately your case is a bit tricky, because you are invoking a method within this context when you do save(items);, this means the transaction interceptor will not intercept such invocation, therefore you have the possibility to inject the service to a field hold by itself and invoke it on the injected service instead of this forcing the invocation to that method be intercepted by the transaction interceptor, please try the following implementation:
#Service
public class DefaultAnalyzerService implements AnalyzerService {
#Inject
AnalyzerDao analyzerDao;
#Inject
AnalyzerService analyserService;
#Transactional
#Override
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
analyserService.save(items);
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Override
public void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
Another thing that changed was the visibility of save(List<SomeType> items), that is public now on in order to be intercepted by transaction interceptor and an interface was extracted. This is needed due to limitations with spring, but you can use AspectJ to handle such interceptor, therefore please take a look here.
Data won't appear in the database until the transaction is committed. For #Transactional methods, the transaction is committed by Spring after returning from the method.
By the way, #Transactional on private methods has no effect, so Scanner.scan() is not transactional at all.
I have a java application created using spring+hibernate.
I have a code like this:
public class EmployeeDAO extends AbstractHibernateDAO {
public void save(Employee emp) throws HibernateException {
super.save(emp); // inside this method, it calls hibernate session.save(). This super.save method can throws HibernateException
doSometingElse(emp); // inside this method, it doesn't call any hibernate methods. It can throws Exception too.
}
}
I would like to make EmployeeDAO.save method as an atomic method in transactional view.
If super.save(emp) succeed but doSomethingElse(emp) failed (by throwing Exception) then I want the Employee record inserted in super.save(emp) be rollbacked.
How to do this?
All you need to do is annotate the method with #Transactional like this:
public class EmployeeDAO extends AbstractHibernateDAO {
#Transactional
public void save(Employee emp) throws HibernateException {
super.save(emp); // inside this method, it calls hibernate session.save(). This super.save method can throws HibernateException
doSometingElse(emp); // inside this method, it doesn't call any hibernate methods. It can throws Exception too.
}
}
That way if an exception is thrown in the EmployeeDAO save the whole method worth of hibernate operations will be rolled back.
If you prefer for all the methods in this class to run in their own transaction, then annotate the class #Transactional instead.
You'll also need to make sure you do have a transaction manager configured.
If you are using Spring Java configuration you'll want your transaction manager to look something like this:
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory());
transactionManager.setDataSource(datasource());
transactionManager.setJpaDialect(new HibernateJpaDialect());
return transactionManager;
}