Recovering from hibernate optimistic locking exception - java

I have a method like this:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
doSomeWork();
}
}
I was expecting that by using REQUIRES_NEW transaction propagation and the recursion shown, the StaleObjectStateException would eventually clear but this isn't the case.
How do I recover from this exception?

Turns out there's a bit of a 'gatcha' which I've overlooked...
From spring docs
In proxy mode (which is the default), only 'external' method calls
coming in through the proxy will be intercepted. This means that
'self-invocation', i.e. a method within the target object calling some
other method of the target object, won't lead to an actual transaction
at runtime even if the invoked method is marked with #Transactional!
Because I've been recursing internally, the transaction demarcation wasn't getting applied.
The solution is to recurse via the proxy, like this...
#Autowired
private ApplicationContext applicationContext;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
applicationContext.getBean(this.getClass()).doSomeWork();
}
}

Related

Is it possible to nest transactions with TransactionManager?

I have the following code:
public ResultProcessDTO process() {
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return transactionTemplate.execute(status -> {
...
SomeEntity entity = service.findById(id);
...
otherBean.someMethod();
...
});
}
And, in another bean:
public void someMethod() {
...
service.save(entity);
}
I need someMethod() to be REQUIRES_NEW, to perform the save and commit the transaction regardless of what happens with the rest of the process().
I've already tried #Transactional to leave this up to Spring, but the process() is triggered via KafkaListener, which leads me to have problems with lazy load entities that are fetched along the way.
For example, if I call entity.getChildList() I get a LazyInitializationException. This exception is not thrown if I use TransactionTemplate.
Any suggestions on what I'm doing wrong or how to make it work as I hope?

Spring Autonomous Transaction - Execute secondary then throw primary

I am having trouble with Spring Transactions. I need my catch block to persist some state on a forked/autonomous transaction then throw an exception to rollback whatever there was done on the primary transaction.
#Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
rollbackFor = {Exception.class})
public void createApplication() {
try {
saveState(0);
} catch(Exception e) {
saveState(1);
throw e;
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveState(Integet stateId) {
...
}
The issue with this setup is that everything gets rollbacked (as mentioned in How to partially rollback data in spring boot, errors to be persited). Is there a way to fork transactions in Spring or this way is fundamentally incorrect?
I've also tried pairing Propagation.REQUIRED with Propagation.NESTED without avail.
This is not working because you are having your second #Transactional method in the same class. Remember that #Transactional only works if the call goes through the proxy. Just move this saveState to another class and I am sure it is going to work because the rest of your code is perfectly correct.

How to rollback transaction on calling #Transactional and non-Transactional method in same and different Service?

I am using spring data rest and Spring JPA. I am having one method which update one database table.
#Autowired InvoiceClient;
#Override
#Transactional
public String doBilling(String x){
//get date from TableOne
Bill bill = billsRepository.getBill(x);
if(bill.isPaid()){
generateInvoice();
}
bill.setPaymentDate(new Date());
return "SUCCESS";
}
generateInvoice is non Transactional method which calls #Transactional method from other service.
public void generateInvoice(){
invoiceClient.generateInvoice();//this is #Transactional, make changes in TableTwo
}
In case of any exception in generateInvoice method whole transaction is rolled back.
Now I want to add one more method which will have list of bill numbers. I call doBilling method in loop to do billing for all the bills.
#Override
#Transactional(readOnly = false, rollbackFor = {Throwable.class}, propagation = Propagation.REQUIRED)
public String doBillingForAll(List<String> tx){
for(String x: tx){
doBilling(x);
}
}
But now in case of any exceptions in doBilling method, all the setPayment methods are getting rolled back but generateInvoice is persisted.
I want to rollback generateInvoice also. How can I do it?
You don't need to define a rollbackFor = {Throwable.class}.
By default all RuntimeException do a rollback when using #Transactional.
It can be that because you are using and intermediate non #Transactional annotated method, the main Transaction is suspended and a nested one is created.
Try to put #Transactional in your public void generateInvoice() then Propagation.REQUIRED should be applied with rollback of your invoices

#Transactional(isolation = Isolation.SERIALIZABLE) retry mechanism

#Transactional(isolation = Isolation.SERIALIZABLE)
I have that annotation on several methods in my spring project. If there is an exception due to the "serialize access problems" what is the best approach if I want to retry the specific transaction. There is annotation #Retryable but it is not very straightforward to me how to use it so that the transaction will rollback and then retry only for that specific exception and just rollback for the other runtime exceptions. Thanks in advance.
A simple solution is to have a method that is the "entry point" for performing your logic; which delegates the actual logic to a transactional method. Typically a nice way of doing this is to have one class that has the Transactional annotations and that does the work and another which is the interface for clients to interact with that delegates; providing a form of indirection.
private static final int MAX_RETRY = 5;
public void doWork(T... parameters) {
doWork(0, parameters);
}
private void doWork(int retryLevel, T... parameters) {
if (retryLevel == MAX_RETRY) {
throw new MaximumRetryCountException(); //or any other exception
} else {
try {
//Get your Spring context through whatever method you usually use
AppContext().getInstance().getBean(classInterestedIn.class).doTransactionalMethod(parameters);
} catch (ExceptionToRetryFor e) {
doWork((retryLevel + 1), parameters);
}
}
}
#Transactional(isolation = Isolation.SERIALIZABLE)
public void doTransactionalMethod(parameters) {
...
}
Please note you may run into problems calling a Transactional method from a different method within that same class (i.e. calling this.doTransactionalMethod()) hence the invocation of Transactional Method is through the Spring Application Context. This is due to the way Spring AOP wraps classes to engage transactional semantics. See: Spring #Transaction method call by the method within the same class, does not work?

Java Spring #Transactional method not rolling back as expected

Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.
I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)
Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}

Categories