'@Transactional annotation not working as expected
I need some help trying to debug why the transaction management of my spring boot app is not working.
The basic idea is that I have 2 tables I would like to write something in. When anything goes wrong in one of the 2 tables, the transaction should be rolled back and nothing should be written to the database.
Here is a simplified version of the code:
@Transactional
public void archiveTask(String taskId) {
OffloadedRun run = new OffloadedRun();
run.setStartDateTime(LocalDateTime.now());
calculationRunRepository.save(run);
List<SingleContractCalculationResults> activeResults = contractCalculationResultAccessService.get(taskId);
for (SingleContractCalculationResults result : example) {
for (Map.Entry<String, ContractResults> entry : result.getResultsPerScenario().entrySet()) {
String scenario = entry.getKey();
ContractResults results = entry.getValue();
OffloadedCalculationResult offloadedCalculationResult = new OffloadedCalculationResult();
// offloadedCalculationResult.setOffloadedRun(run);
offloadedCalculationResult.setContractId(result.getContractId());
calculationResultRepository.save(offloadedCalculationResult);
}
}
}
The classes that I execute the save methods on are Spring Data JPA repositories that are defined like this:
public interface CalculationRunRepository extends JpaRepository<OffloadedRun, String> {
}
the line I commented out is a mandatory column. I do this to enforce a ConstraintViolationException to test what happens on an exception when saving something in the second table.
What happens is that the first entity is saved successfully, which should not have happened. I'm trying to figure out why this is.
My spring boot application is configured with @EnableTransactionManagement to enable the @Transactional annotations in my own services (like this one).
I changed the logging level for org.springframework.transaction.interceptor to TRACE to see what's going on:
o.s.t.i.TransactionInterceptor : Getting transaction for [be.sodemo.calculator.offloading.TaskArchiverImpl.archiveTask]
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
o.s.t.i.TransactionInterceptor : Completing transaction for [be.sodemo.calculator.offloading.TaskArchiverImpl.archiveTask]
o.h.e.j.s.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
o.h.e.j.s.SqlExceptionHelper : Column 'run_id' cannot be null
o.h.e.j.b.i.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'run_id' cannot be null
at sun.reflect.GeneratedConstructorAccessor2599.newInstance(Unknown Source) ~[?:?]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java) ~[?:1.8.0_102]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[?:1.8.0_102]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.40.jar:5.1.40]
After this
I'm not sure what the logging would look like if transaction management works properly but it looks like it's completing every transaction.
Does anyone have an idea what I can try next to see what's going wrong?
Edit: Until now I have been using a MySQL database. I noticed that when I tested this exact same code on a H2 database, it seems like the rollback works as intended. The only difference I see with that is that it throws another vendor-specific exception.
I've tried explicitly setting the rollbackFor attribute on the @Transactional annotation like so:
@Transactional(rollbackFor = {Exception.class, MySQLIntegrityConstraintViolationException.class})
But even that didn't cause a rollback.
Edit:
These are my spring boot settings related to JPA/Hibernate:
spring:
jpa:
hibernate:
ddl-auto: none
dialect: org.hibernate.dialect.MySQL5Dialect
database: mysql
properties:
hibernate:
order_inserts: true
jdbc:
batch_size: 50
datasource:
url: jdbc:mysql://localhost/local-test-db
driver-class-name: com.mysql.jdbc.Driver
Solution 1:[1]
I am not sure why you are using String for your id type in repository <OffloadedRun, String>. Your OffloadedRun domain has id with String type? It should match with type of your id field in OffloadedRun domain. Make sure for the case. To confirm, Could you please post your OffloadedRun domain code also?
Solution 2:[2]
You have to use org.springframework.orm.hibernate4.HibernateTransactionManager.
You might be using org.springframework.orm.jpa.JpaTransactionManager.
Solution 3:[3]
Please verify whether you are using single dataSource or multiple dataSources in your application.
If you are using single dataSource then @Transactional will pick that single dataSource by default else it will pick any one from multiple dataSources and this kind of untraceable issue occurs.
Please have a look at @Transaction annotation with different data sources
I have resolved the issue using
/* This code is present in @Configuration class */
@Bean(name = "postgresDataSource")
DataSource postgresDataSource(){
// DataSource configuration code
}
@Bean(name = "postgresTransactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("postgresDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/* This code is present in @Service class */
@Override
@Transactional("postgresTransactionManager")
public void save(Map queueMsg) {
// Transaction specific code
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Infinite |
| Solution 2 | Ankit Gupta |
| Solution 3 |
