Spring Boot #Transactional Attribute with Multiple DataSource - java

Hi I have two different methods and they use different datasources and transaction manager.I use #Transactional attribute and what I want, if my second method throws exception than my first method do its rollback. But it is not working, first method cant rollback. What am I missing?
#Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED,
transactionManager = myTransactionManager", propagation = Propagation.REQUIRED)
public void saveTest(TblTest testEntity) {
mySecondDBSource.saveTest2(testEntity);(use MyTransactionManager2) //Do job
testTableRepository.save(testEntity); (Use myTransactionManager) //throws Exception
}
//in mySecondDBSource class there is another method
#Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED,
transactionManager = "MyTransactionManager2", propagation = Propagation.REQUIRED)
public void saveTest2(TblTest2 testEntity) {
testTableRepository2.save(testEntity);
}

Spring Data offers a way to handle so called chained/distributed transactions via ChainedTransactionManager.
See spring-transactional-with-a-transaction-across-multiple-data-sources.
Here is also a simple guide on medium.

Related

How does rollback() in JDBC Template work?

I am learning JDBS template and wonder if there is a way to rollback operation.
It is easy to do it using JDBC, just
conn.setAutoCommit(false);
// DoinП stuff
con.rollback();
But is there a way do the same using JDBS template?
You should use the spring-boot-starter-data-jdbc like in this guide
Thanks to this spring-boot-starter-data-jdbc and to the spring boot autoconfiguration mechanism, a jdbc template bean and a transaction manager bean will be created for you.
Then you can use #Transactional annotation on non private method to wrap your code into a transaction :
#Transactional
public void methodA() {
jdbcTemplate.update(...);
jdbcTemplate.update(...);
}
or you can use a transacionTemplate to handle the transaction programatically
#Service
public class ServiceA {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void methodA() {
transactionTemplate.execute(new TransactionCallback<>() {
public Object doInTransaction(TransactionStatus status) {
jdbcTemplate.update(...);
jdbcTemplate.update(...);
return jdbcTemplate.query(...);
}
});
}
}
If you want to execute multiple statements and want to be sure all or none executed, you can use transactions.
First, you have to create a session and begin the transaction.
After, you can execute statements and commit the transaction.
But, in case of problems, you can rollback (undo previous statements).
With spring you can use #Transactional annotation
#Transactional
public void transaction() { // Spring will begin transaction
doTransactionalStatements(); // Spring will rollback in case of RuntimeException
} // Spring will commit transaction

Why transaction not getting rollbacked in Spring JPA for REQUIRED propagation level?

I have two methods in the JPA repository. Both the methods have propagation level as REQUIRED
The methods are used to persist entity objects using Hibernate to Postgresql
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
try {
persistLineManager();
}
catch( Exception e ) {
// some task
}
}
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
public void persistLineManager() {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
}
As per Spring docs when propagation level is REQUIRED both methods will run in the same transaction. In my code, I am intentionally throwing the Exception to trigger the rollback but still, both the entities are getting persisted. But I believe both the operations should be rollbacked. Please correct if my understanding is incorrect and let me know the correct way to
rollback both the operations.
PROPAGATION_REQUIRES_NEW: [ from spring Docs ]
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status.
PROXYFICATION
In your service, you created 2 methods, both #Transactional. When you create your bean, spring will create a proxy to add for you at runtime the behavior for a Transactional method. Let's dive deeper:
This proxyfication is illustrated by the image. Any caller form the outside world will not directly talk to you, but to your proxy. And then, the proxy will call you to execute the code of your service.
Now, this "Any caller form the outside world will not directly talk to you" is very important. If you make an inner call, like you do in persistEmployee which is calling persistLineManager, then you do not pass through the proxy. You call directly your method, NO PROXY. Therefor, the annotations at the top of your persistLineManager method are not read.
So, when persistLineManager is throwing a RuntimeException, the exception is catched directly by your caller persistEmployee, you go directly in your catch. As there is no proxy, there is no rollback because the transactional proxy did not catch the exception.
If you do only this, you will have a rollback occurring:
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
persistLineManager();
// Don't catch and the exception will be catched by the transaction proxy, which will rollback
}
public void persistLineManager() {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
}
By default, #Transactional rollback for a RuntimeException
TRANSACTION TEMPLATE
Suppose you still want both method to be transactional independently, what you can do is using the TransactionTemplate. Here is an example:
class MyService {
// Have a field of type TransactionTemplate
private TransactionTemplate template;
// In the constructor, Spring will inject the correct bean
public MyService(PlatformTransactionManager transactionManager) {
template = new TransactionTemplate(transactionManager);
// Set this here if you always want this behaviour for your programmatic transaction
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
// Here you start your first transaction when arriving from the outside
#Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
Employee employee = new Employee("Peter", "Washington DC");
entityManager.persist(employee);
// Inner call
try {
persistLineManager();
} catch (RuntimeException e) {
// Do what you want
}
}
public void persistLineManager() {
// Here, ask to your transactionTemplate to execute your code.
template.execute(status -> {
Employee lineManager = new Employee("John", "NYC");
entityManager.persist(lineManager);
if(lineManager != null) // intentionally! To trigger rollback
throw new RuntimeException("Rollback!");
return null;
});
}
}
I haven't tested everything, you might face some errors, but I hope you get the idea.
PROPAGATION
Let me add a last part about the difference between PROPAGATION_REQUIRED and PROPAGATION_REQUIRES_NEW:
PROPAGATION_REQUIRED:
Either I have no transaction, then I create one
Or There is a running transaction, and I join it.
PROPAGATION_REQUIRES:
In any situations, whether a transaction is running or not, I create a new one.
Example:
A client is entering in my transactional method, with PROPAGATION_REQUIRED. It creates a transaction name "TA".
This transactional method calls a method, which is also transactional, but PROPAGATION_REQUIRES_NEW. It creates a second transaction named "TB"
With have now: "client" -> "TA" -> "TB"
But the second method triggers a rollback. In that case, only "TB" will be rolledback, as "TA" and "TB" are 2 differents transactions.
So, in DB, I will persist every operation that has been made in "TA", but not in "TB".
Hope it helps

Entities are not persisted into Database if #transactional is attached to certain method

I am a new user of spring and hibernate.
I have a service class, which is called by a controller.
The call flow is like this:
controll calls myService.create method, then
myService.create calls myService.persistEnity method.
within the myService.persistEnity method, there is a dao object which persist entity to database.
The issue I found is:
if I attach the #transactional annotation to "myService.create" method, entities will be saved to databases.
however,
if I attach the #transactional annotation to "myService.persistEnity" method entities are not saved to databases, and I don't see any error message.
My question is:
why would this happen? (Is this because of the call flow? i.e. #transactional has to be attached to the first method of service class that is invoked by controller class in order to work?)
Thanks.
psudo Code is as below:
#org.springframework.stereotype.Service("myService")
public class MyService {
#Autowired
private MyDao dao;
// if the #Transactional is only put here as this, entities will NOT be saved to database.
#Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public MyEntity persistEnity(MyEntity toBeSaved) {
MyEntity entity = dao.persistEntity(toBeSaved);
return entity;
}
// if the #Transactional is only put here, entities will be saved to database.
// #Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public ServiceObject create(ServiceObject serviceObject, User user) {
MyEntity entity = convertToEntity(serviceObject);
entity = persistEnity(dao);
return convertTOServiceObject(entity);
}
...
}
Because method convertToEntity creates an object type MyEntity in create, and if this method is transactional, the object is being created attached to the session. But if the method is not transactional, you create the new object out of session and send it to persistEnity, but it's not attached so dao cannot save it without new attaching to session.
Leave transactional on method where an object is created or attach it into session in persistEnity method.

Does a Nested #Transactional(propagation = Propagation.REQUIRES_NEW) Create a New Hibernate Session?

When starting a new transaction, using #Transactional(propagation = Propagation.REQUIRES_NEW) in a current transaction, that was started using #Transactional(propagation = Propagation.REQUIRED), is a new Session created for the new Transaction?
Is there a way to use only one session for all transactions if the above is true?
Here is a visual example:
#Transactional(propagation = Propagation.REQUIRED
public void startParentTransaction() {
ChildObject childObject = newChildTransaction();
childObject.lazyLoadedAttribute();
ChildObject childObject = newChildTransaction();
childObject.lazyLoadedAttribute();
}
#Transactional(propagation = Propagation.REQUIRES_NEW
public ChildObject newChildTransaction() {
ChildObject childObject = callRepositoryOrDao();
childObject.changeState();
return childObject;
}
Will the call to the childOjbect.lazyLoadedAttribute() work? Or will a LazyInitializationException be thrown?
Can we have all the transactions happen in one Session?
You're bound to Spring #Transactional annotation.
Normally, Propagation.REQUIRE_NEW will start a new transaction even there's existint transaction.
But if you call the newChildTransaction locally (just is within the same method), then the spring AOP does not have a chance to interact with the #Transaction annotation.
Hence in this case, the newChildTransaction() use the existing transaction

How to run two Spring transactions in a single hibernate Session?

I know, that there is a way to descend to a low level - get connection and perform two transaction's by hand in a single hibernate session.
But the question is - how to invoke second nested transaction in the same Session via #Transactional annotations (not using "low level hacks" or handwrited custom transaction management)?
Some possible code:
#Service
public class DoubleTransaction {
#Autowired
private SessionFactory sf;
#Autowired
private NestedTeHandler nestedHandler;
#Transactional
void invokeTransaction() {
Session cs = sf.getCurrentSession();
Session nestedCs = nestedHandler.invokeNested(sf);
System.out.println(cs == nestedCs);
}}
#Service
public class NestedTeHandler {
#Transactional
Session invokeNested(SessionFactory sf) {
return sf.getCurrentSession();
}}
You might be able to do that with
#org.springframework.transaction.annotation.Transactional(propagation = Propagation.NESTED)
on NestedTeHandler.invokeNested. See documenation at https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html.
See also this question: Differences between requires_new and nested propagation in Spring transactions.

Categories

Resources