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.
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
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.
Let me add a last part about the difference between PROPAGATION_REQUIRED and PROPAGATION_REQUIRES_NEW:
Example:
Hope it helps