Using #Transactional on service method, which updates multiple repositories - java

Spring boot, postgres, spring jpa.
Have a service, which is trying to store changes across multiple repositories:
class Service {
#Transactional
public void doStuff() {
repo1.delete(...);
repo2.saveAll(...);
repo1.save(...);
}
}
This operation requires to be rolled back if anything fails.
Here I struck into two things:
If I add a throw RuntimeException somewhere in the middle of that method, all things before it don't get rolled back.
In regular flow I get
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
My configuration is:
#Configuration
#EnableTransactionManager
#EntityScan
#EnableJpaRepositories
public class DataConfig {}
Also trying to use the TransactionTemplate bean with it's execute method. Manage to overcome the first issue, but still fail with the second one.

You can try by adding #Transactional(rollbackFor = Exception.class) this will rollback even if you have an exception in your code
class Service {
#Transactional(rollbackFor = Exception.class)
public void doStuff() {
repo1.delete(...);
repo2.saveAll(...);
repo1.save(...);
}
}

Related

Rollback changes done to a MariaDB database by a spring test without #Transactional

I have a Spring service that does something like that :
#Service
public class MyService {
#Transactional(propagation = Propagation.NEVER)
public void doStuff(UUID id) {
// call an external service, via http for example, can be long
// update the database, with a transactionTemplate for example
}
}
The Propagation.NEVER indicates we must not have an active transaction when the method is called because we don't want to block a connection to the database while waiting for an answer from the external service.
Now, how could I properly test this and then rollback the database ? #Transactional on the test won't work, there will be an exception because of Propagation.NEVER.
#SpringBootTest
#Transactional
public class MyServiceTest {
#Autowired
private MyService myService;
public void testDoStuff() {
putMyTestDataInDb();
myService.doStuff(); // <- fails no transaction should be active
assertThat(myData).isTheWayIExpectedItToBe();
}
}
I can remove the #Transactional but then my database is not in a consistent state for the next test.
For now my solution is to truncate all tables of my database after each test in a #AfterEach junit callback, but this is a bit clunky and gets quite slow when the database has more than a few tables.
Here comes my question : how could I rollback the changes done to my database without truncating/using #Transactional ?
The database I'm testing against is mariadb with testcontainers, so a solution that would work only with mariadb/mysql would be enough for me. But something more general would be great !
(another exemple where I would like to be able to not use #Transactional on the test : sometimes I want to test that transaction boundaries are correctly put in the code, and not hit some lazy loading exceptions at runtime because I forgot a #Transactional somewhere in the production code).
Some other precisions, if that helps :
I use JPA with Hibernate
The database is create with liquibase when the application context starts
Others ideas I've played with :
#DirtiesContext : this is a lot slower, creating a new context is a lot more expensive than just truncating all tables in my database
MariaDB SAVEPOINT : dead end, it's just a way to go back to a state of the database INSIDE a transaction. This would be the ideal solution IMO if i could work globally
Trying to fiddle with connections, issuing START TRANSACTION statements natively on the datasource before the test and ROLLBACK after the tests : really dirty, could not make it work
Personal opinion: #Transactional + #SpringBootTest is (in a way) the same anti-pattern as spring.jpa.open-in-view. Yes, it's easy to get things working at first and having the automatic rollback is nice, but it loses you a lot of flexibility and control over your transactions. Anything that requires manual transaction management becomes very hard to test that way.
We recently had a very similar case and in the end we decided to bite the bullet and use #DirtiesContext instead. Yeah, tests take 30 more minutes to run, but as an added benefit the tested services behave the exact same way as in production and the tests are more likely to catch any transaction issues.
But before we did the switch, we considered using the following workaround:
Create an interface and a service similar to the following:
interface TransactionService
{
void runWithoutTransaction(Runnable runnable);
}
#Service
public class RealTransactionService implements TransactionService
{
#Transactional(propagation = Propagation.NEVER)
public void runWithoutTransaction(Runnable runnable)
{
runnable.run();
}
}
In your other service wrap the external http calls with the #runWithoutTransaction-Method, e.g.:
#Service
public class MyService
{
#Autowired
private TransactionService transactionService;
public void doStuff(UUID id)
{
transactionService.runWithoutTransaction(() -> {
// call an external service
})
}
}
That way your production code will peform the Propagation.NEVER check, and for the tests you can replace the TransactionService with a different implemention that doesn't have the #Transactional annotations, e.g.:
#Service
#Primary
public class FakeTransactionService implements TransactionService
{
// No annotation here
public void runWithoutTransaction(Runnable runnable)
{
runnable.run();
}
}
This is not limited to Propagation.NEVER. Other propagation types can be implemented in the same way:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void runWithNewTransaction(Runnable runnable)
{
runnable.run();
}
And finally - the Runnable parameter can be replaced with a Function/Consumer/Supplier if the method needs to return and/or accept a value.
This is bit of wild idea, but if you are using mysql database, then maybe switch to dolt for tests?
Dolt is a SQL database that you can fork, clone, branch, merge, push and pull just like a git repository.
You can wrap it as testcontainers container, load necessary data on start and then, on start of each test run dolt reset.

How Propagation REQUIRED works in spring boot?

I am just trying to understand the transaction propagation in spring: using jpa, postgres, validators and web starter in my project. Propogation REQUIRED says:
when one of these logical transactions is rolled back, all the logical transactions of the current physical transaction are rolled back.
but when I am throwing exception in insertUserKumar() it should not persist data in database as per documentation but its getting persisted.
my properties are below defined
logging.level.sql=debug
spring.datasource.generate_unique_name=false
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=dummy
spring.datasource.password=dummy
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.hikari.auto-commit=false
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
some will say you might have missed #EnableTransactionManagement I tried it with or without that. but two records are getting persisted into database. Can anyone help me?
#Slf4j
#Component
#RequiredArgsConstructor
public class AppRunner implements CommandLineRunner {
private final UserRepository userRepository;
#Override
public void run(String... args) throws Exception {
insertUserBunty();// this transaction should fail but its getting persisted
}
#Transactional(propagation = Propagation.REQUIRED)
void insertUserBunty() {
User bunty = new User(null, "Bunty");
userRepository.save(bunty);
insertUserKumar();
}
#Transactional(propagation = Propagation.REQUIRED)
void insertUserKumar() {
User kumar = new User(null, "Kumar");
userRepository.save(kumar);
throw new RuntimeException("This will rollback both insert Bunty and Kumar");
}
}
The reason is that #Transactional does not work when you call the method from another method in the class. The reason being the way Spring handles the transactionality features. It is basically handled by a Proxy of your class and thus #Transactional annotation only has an effect when the method is called by a method outside your class. Check details at:
https://medium.com/javarevisited/spring-transactional-mistakes-everyone-did-31418e5a6d6b

#Caching method called from spring boot test [annotated with #Transactional] not working

I'm using redis caching and spring boot annotations[#Cacheable and #CahePut],
I made RedisManager transactionAware, which will use the outer transaction[callee of caching layer]
#Bean
public RedisCacheManager cacheManager() {
RedisCacheManager rcm =
RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(cacheConfiguration())
.transactionAware()
.build();
return rcm;
}
while testing as below, I'm using embedded redis-:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase
#Transactional
public class RoleServiceImplTest extends TestingProfile {
#Before
public void setup() throws Exception {
//setup server and services
redisServer = new RedisServer(redisPort);
redisServer.start();
}
#Test
public void getUsersForRoleForTemplateRole() {
// call to caching layer methods directly annotated with #Cachable
}
...
Both times [ with and without #Transactional ] spring calls cache.put(key,result) without exception but it only persists values in case of without #Transactional.
Couldn't find much on internet, kudos to any help in advance.
In short just put #Commit or Rollback(false) annotation over your class or test method.
Spring by default rollback every Transaction after the test method.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-tx
In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener, which is configured by default, even if you do not explicitly declare #TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded with #ContextConfiguration semantics (further details are provided later). In addition, you must declare Spring’s #Transactional annotation either at the class or the method level for your tests.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/transaction/TransactionalTestExecutionListener.html
Declarative Rollback and Commit Behavior
By default, test transactions will be automatically rolled back after completion of the test; however, transactional commit and rollback behavior can be configured declaratively via the #Commit and #Rollback annotations at the class level and at the method level.

Spring UnexpectedRollbackException in nested #Transactional method

I have a dao class (MyDao) which is marked with #Transactional annotaion (on class level) with no additional parameters. In this dao class I have a method which in some case needs to throw a checked exception and perform a transaction rollback. Something like this:
#Transactional
public class MyDao {
public void daoMethod() throws MyCheckedException() {
if (somethingWrong) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new MyCheckedException("something wrong");
}
}
This works perfectly fine. However, this dao method is called from a service method, which is also marked as #Transactional:
public class MyService {
#Autowired
private MyDao myDao;
#Transactional
public void serviceMethod() throws MyCheckedException {
myDao.daoMethod();
}
}
The problem is that when daoMethod() is called from serviceMethod() and marks the transaction as rollback only, I get an UnexpectedRollbackException.
Under the hood, Spring creates two transactional interceptors: one for MyDao and one for MyService. When daoMethod() marks the transaction for rollback, the interceptor for MyDao performs the rollback and returns. But then the stack moves to the interceptor for MyService, which finds out about the previous rollback, and throws the UnexpectedRollbackException.
One solution would be to remove the #Transactional annotation from MyDao. But this is not feasible right now because MyDao is used at a lot of places and this could cause errors.
Another solution would be to not set the transaction as rollback only in daoMethod() but rather mark serviceMethod() to revert the transaction on MyCheckedException. But I don't like this solution because I have a lot of these "service methods" and I would have to mark all of them explicitly to rollback on that exception. Also everyone who would add a new service method in the future would have to think of this and therefore it creates opportunities for errors.
Is there a way I could keep setting the transaction as rollback only from daoMethod() and prevent Spring from throwing the UnexpectedRollbackException? For instance, with some combination of parameters isolation and propagation?
I figured it out. I have to explicitly tell also the "outer" interceptor, that I want to rollback the transaction. In other words, both interceptors need to be "informed" about the rollback. This means either catching MyCheckedException in serviceMethod() and setting the transaction status to rollback only, or to mark serviceMethod() like this #Transactional(rollbackFor=MyCheckedException.class).
But as I mentioned in the OP, I want to avoid this because it's prone to errors. Another way is to make #Transactional rollback on MyCheckedException by default. But that's a completely different story.
Throwing an exception inside the transaction already trigger a rollback, using setRollbackOnly() is redundant here and that's probably why you have that error.
If the Transactionnal on the DAO is set up with Propagation.REQUIRED which is the default, then it will reuse an existing transaction if there is already one or create one if there is none. Here it should reuse the transaction created at the service layer.

Transaction not rollbacked

I have a set of operation i would like to be rollbacked if there are an error.
My class
public class BSException extends RuntimeException{
...
}
public class saleFacade{
public update(){
for (){
try{
renewSale();
}
catch(BSException){
logger.error();
}
}
}
#Transactional
public renewSale(){
try{
findSale(); // read only Transactional
xxx.renewSpecialSale();
}
catch(Exception e){
logger.error(...);
}
}
}
public class xxx(){
public void renewSpecialSale(){
payFee(); //write to db
if(error){
throw new BSException();
}
}
#Transactional(propagation = Propagation.REQUIRED)
public payFee(){
try{
...
}
catch(BsException e){
...
}
catch(Exception e){
...
}
}
}
#Configuration
#EnableTransactionManagement
public class DBConfiguration{
#Bean(name = "dataSource")
public BasicDataSource dataSource(){
...
}
}
Inn renewSpecialSale error is throw.
In the renewSale method, if there is an error, i would like to rollback.
Right now nothing is rollbacked
any idea?
If you catch the exception before it leaves the method, then there is no way the proxy wrapping the method can know that an exception was thrown.
Either remove the try-catch entirely or rethrow the exception so that the exception can leave the method that you marked #Transactional (and get intercepted by the proxy), and the rollback will take place.
I recommend removing exception-handling from all these methods. Set up a central exception handler so that anything thrown from the controllers gets caught and logged, and otherwise let exceptions get thrown.
Make sure each of these classes that does something transactional is annotated separately, if you annotate on the method-level then each method that does something transactional should be annotated. Calling a transactional method from a non-transactional method on the same object doesn't go through the proxy (the proxy intercepts only those calls coming in from outside the object, and only then if that method is marked as transactional) so it isn't part of a transaction (+1 to Peter's answer for pointing this out).
I have no idea what your error flag is doing, it seems odd. Spring services shouldn't have state. If you fix the exception-handling you shouldn't need error flags.
The problem comes from your nesting of Method calls and the usage of #Transactional. By default Springs Transaction Management
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. Spring Transaction Management
This means, that the call of
xxx.payFee()
is not surrounded by a transaction when it's called through
saleFacade.update() -> saleFacade.renewSale() -> xxx.renewSpecialSale()
As far as I got it, you have at least these options
Mark xxx.renewSpecialSale() as #Transactional
Mark saleFacade.update() as #Transactional
Mark both classes xxx and saleFacade #Transactional
Also you can create your own custom exception class and throw it.
Just throw any RuntimeException from a method marked as #Transactional.
By default all RuntimeExceptions rollback transaction whereas checked exceptions don't. This is an EJB legacy. You can configure this by using rollbackFor() and noRollbackFor() annotation parameters:
#Transactional(rollbackFor=Exception.class)
This will rollback transaction after throwing any exception.
#Transactional(rollbackFor = MyCheckedException.class)
public void foo() {
throw new RuntimeException();
}
remove try catch block from your method and put below line of code
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
use this line of code insted of
#Transactional(propagation = Propagation.REQUIRED)
In above code spring container handle whole transaction management and here if we provide rollback attribute it automatically manage if any kind of exception occur it will rollback your transaction
and make sure that you have entry of transaction in your configuration file as below
<tx:annotation-driven />
and also
I hope it will sure help you

Categories