Spring transactions: inner transaction changes are "rolled back" by outer transaction (or maybe jpa misuse) - spring

I have a simple code spring+jpa which looks correct at first glance:
#Transactional(propagation=REQUIRED)
public void outer(long id) {
MyEntity entity = myRepo.findById(id);
try {
// doing something that changes entity
// and may throw exception
doInOuter(entity);
} catch (Exception ex) {
anotherSpringService.inner(entity);
throw ex; // this rollbacks outer transaction for sure
}
}
#Transactional(propagation=REQUIRES_NEW)
public void inner(MyEntity entity) {
// doing something that changes entity
}
Let's consider the case when exception happens during doInOuter(). I would expect that in a catch block in the inner()
outer transaction gets suspended
inner transaction commits the changes
inner transaction closed
exception is thrown in the catch block
outer transaction is rolled back
The changes in the database contain the changes done by inner()
Unfortunately that is not what happens. All the changes in both inner/outer are completely "rolled back". I put the quotes because I don't think it happens because of the rollback itself.
If I change the inner to retrieve the entity one more time:
#Transactional(propagation=REQUIRES_NEW)
public void inner(MyEntity entity) {
entity = myRepo.findById(entity.getId());
// doing something which changes entity
}
The code starts to work as expected and I do see the changes done by inner() in the database.
Can anyone explain me why that works and why the first approach doesn't work as expected?

The Hibernate session is bound to the transaction, and the entity is bound to the session. So, when you pass the entity to the inner method, since it has been loaded from the outer transaction, it stays bound to the outer session.
You shouldn't share the entity between transactions, as you discovered.

Related

How to rollback transaction invoked with jpa entity listeners

I'm using jpa , spring data and entity listeners to audit my entities precisely on postUpdate , postPersist , PostRemove
This is a pseudo code of my entity listener class
public class EntityListener extends AuditingEntityListener {
#PostUpdate
public void postPersist(Object auditedEntity) {
writer.saveEntity(auditedEntity,"UPDATE");
}
This the pseudo code of the Writer class
public class Writer {
#Async
public void saveEntity(Object auditedEntity, String action) {
try {
//some code to prepare the history entity
historyDAO.save(entity);
} catch (Exception e) {
}
}
when an exception is thrown in Writer class , the auditedEntity is updated or inserted however the historyEntity where i store the audit action doesnt
The problem is i need to invoke the saveEntity method in another thread for performance issue (#Async) but in that case a new transaction is open instead of the previously one which opened
how can i solve the rollack issue for both transactions
so when an exception is throwen both historyEntity and auditedEntity not persisted
I understand that you want to rollback both the child and the parent transaction when an exception is thrown from within Writer.saveEntity.
The problem is that the thread with the original transaction would still need to wait for all these complicated operations to finish before it could mark the transaction as committed. You can't easily span a transaction across multiple threads, either.
The only thing you could probably do to speed things up is you could run the logic of generating the history entities in parallel, and then save them all just before the transaction commits.
One way of doing that that I can think of is using a Hibernate interceptor:
public class AuditInterceptor extends EmptyInterceptor {
private List<Callable<BaseEntity>> historyEntries;
private ExecutorService executor;
...
public void beforeTransactionCompletion(Transaction tx) {
List<Future<BaseEntity>> futures = executor.invokeAll(historyEntries);
if (executor.awaitTermination(/* some timeout here */)) {
futures.stream().map(Future::get).forEach(entity -> session.save(object));
} else {
/* rollback */
}
}
}
Your listener code then becomes:
#PostUpdate
public void postPersist(Object auditedEntity) {
interceptor.getHistoryEntries().add(new Callable<BaseEntity> {
/* history entry generation logic goes here */
});
}
(note that the above code is greatly simplified, you could use any other asynchronous execution API, the basic idea is that you need to block in AuditInterceptor.beforeTransactionCompletion, waiting for all the history entries to be generated)
However, I would strongly advise against using the above technique, as it is rather complicated and error prone.
If you look here: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/events/Events.html, you'll find that Hibernate interceptors have more interesting methods that could help you gather auditing info, and that perhaps your implementation could make use of them, possibly avoiding the need for complicated logic altogether (Hibernate already does track changes to fields of individual entities, so you get that information for free).
Why reinvent the wheel, though? If you dig even deeper, you'll find the Hibernate Envers module (http://hibernate.org/orm/envers/, works for both JPA and pure Hibernate) which gives you business auditing out of the box. Envers already digs into the above mechanism, so hopefully the performance issue would go away.
Final note: have you measured how long history entry generation takes? I would guess that executing for loops and if statements might be cheaper than database access operations. If I were you, I wouldn't do any of the above unless I was absolutely sure that's where the performance bottleneck was.

Transaction rollback and save info

In the service layer, I have some method who have a transactional annotation.
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
save db problem issue.
}
}
It seem like if there are a issue, there are roll back... and nothing is saved in the db...
ProcessPaymentException extend Exception
Is there a way to rollback the process in the try but do the save in the catch?
Edit
Nested transaction could be a solution if this link is ok
https://www.credera.com/blog/technology-insights/java/common-oversights-utilizing-nested-transactions-spring/
Existing answer of using ControllerAdvise should help in normal setup that incoming requests are coming through Spring MVC (i.e. through a Controller).
For cases that is not, or you do not want to tie your exception handling logic to Spring MVC, here are some alternatives I can think of
(Here I assume you want to rely on declarative transaction control instead of programmatically controlling transactions yourself)
Separate service/component to save error in different transaction.
In short, you can have a separate service, which create its own transaction by propagation REQUIRES_NEW. e.g.
#Service
public class FooService
#Inject
private ErrorAuditService errorAuditService;
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
errorAuditService.saveErrorAudit(ppe.getErrorText());
throw ppe; // I guess you want to re-throw the exception
}
}
}
#Service
public class ErrorAuditService
#Transactional(propagation=REQUIRES_NEW)
public void saveErrorAudit() {
// save to DB
}
}
One step further, if the error handling it the same for different services, you may create an advise, which will be called when service method throws exception. In that advise, you can save the error in db (using ErrorAuditService), and rethrow the exception.
Because processes of try-catch are wrapped by the same transaction.
The transaction manager do rollback whenever an exception is thrown. So, not thing would be saved.
Is there a way to rollback the process in the try but do the save in the catch?
Yes. Create Exception Handler to save db problem issue after rollback.
this is the idea
#ControllerAdvice
public class HandlerName {
#ExceptionHandler(ProcessPaymentException.class)
public void saveDbIssue(ProcessPaymentException ex) {
// save db problem issue.
}
But it only works if u want to save static data.

Why JDBCTEmplate.batchupdate(sql[]) method not roll back in Spring4 using #transaction annotation?

The below code is not working for rollback when any exception occurs while insertion of records in database.I am using Spring 4 framework and annotation .
*/I am using below code for transaction management and it will not roll back for any exception./
#Transactional(rollbackFor = RuntimeException.class)
public boolean insertBatch(List<String> query) throws SQLException {
boolean flag= false;
try
{
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String[] Sql= query.toArray(new String[query.size()]);
jdbcTemplate.batchUpdate(Sql);
flag=true;
}catch(DataAccessException e )
{
flag=false;
MessageResource.setMessages("Constraints Violation ! CSV data value not matched with database constraints ");
LOGGER.info("CSV file Data not expected as database table structure defination like constraint violation/Data Type lenght/NUll etc for same data value" );
LOGGER.error( "Cause for error: "+ e.getRootCause().getMessage());
LOGGER.debug( "Details explain : "+ e.toString());
throw new RuntimeException("Roll back operation");
//transactionManager.rollback(status);
}
return flag;
}**
Actullay answaer provided by Sir, M.Deinum is below:
Spring uses proxies to apply AOP this will only work for methods called from the outside. Internal method calls don't pass through the proxy hence no transactions and depending on your queries you get one large or multiple smaller commits. Make sure that the outer method (the one called to initiate everything) is transactional. – M. Deinum 14 hours ago
#Transactional(rollbackFor = RuntimeException.class)
This will rollback only if a RuntimeException or a subclass is thrown from the annotated method. If you want to rollback for any Exception (such as SQLException, which is NOT a RuntimeException), you should do:
#Transactional(rollbackFor = Exception.class)
And if you want to try a rollback for whatever error that might happen
#Transactional(rollbackFor = Throwable.class)
Altough in this last case the runtime might be so broken that not even the rollback can complete.
Use Prepared statement from connection object and the do a execute batch object. On the connection object use conn.setAutoCommit(false). Prepeared statement has 4 times better performance than JdbcTemplate for batch insertion of 1000 records.
Reference : JdbcTemplate.batchUpdate() returns 0, on insert error for one item but inserts the remaining item into sql server db despite using #Transactional

Spring + Hibernate how to prevent rollback

I have a code block with this method:
#Transactional(noRollbackFor=Exception.class)
public synchronized Object saveData(){
//...
dao.insert(entity);
//...
}
My dao class is marked ad Transactional
#Transactional
public class Dao {
//...
public <T> void insert(T obj) throws Exception {
getSession().saveOrUpdate(obj);
}
}
I need to prevent any rollback inside this block. However I got this:
2014-02-25 20:47:44 [WARN]SqlExceptionHelper:143 SQL Error: 1205, SQLState: 41000
2014-02-25 20:47:44 [ERROR]SqlExceptionHelper:144 Lock wait timeout exceeded; try restarting transaction
2014-02-25 20:47:44 [ERROR]BigliettiService:? Transazione in errore
org.hibernate.exception.GenericJDBCException: Lock wait timeout exceeded; try restarting transaction
...
2014-02-25 20:47:44 [ERROR]JsonHandlerExceptionResolver:? Transaction rolled back because it has been marked as rollback-only
Why at the end I found a Transaction rolled back?
I had a similar issue just last week. In my case it was another #Transactional annotation from a different method that caused the transaction to be marked as rollback-only.
Did you check for that yet?
Edit: To clarify it a bit more, you didn't post your code from inside your saveData() method. What caused this error for me was calling another method with #Transactional (lacking the noRollback attribute) from inside my method, in your case saveData().
I don't think you can do that. Your transaction is being propagated by default to the inner method and if this method rollbacks your transaction, the outer method can only be notified by a UnexpectedRollbackException that the rollback already happened.. and acknowledge it.
You can change the propagation config to start a new inner transaction but this won't help as it will still be commited independently before reaching the outer transaction.
If you can't set a NoRollback attribute at DAO level, I guess the best solution would be to remove the Transactional attributes from your DAOs and have a layer of facade services calling these DAOs and defining their own rollback strategies.

Jpa save entity after failed delete

I am trying to remove an entity and if it cannot be removed because of a constraint I want to mark it for logical removal.
This is my code:
#Transactional
public void removeEntity(EntityDto e) {
Entity entity = entityRepository.findOne(e.getId());
try {
entityRepository.delete(e.getId());
entityRepository.flush();
} catch (DataIntegrityViolationException ex) {
logger.debug("Logical removal");
entity.setLogicalRemovalDate(new Date());
entityRepository.save(entity);
}
}
After calling the save() method I get this exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.ObjectDeletedException: deleted instance passed to merge
Is there any way I can implement this functionality ?
Thanks.
Not without doing it in a new transaction, with a fresh Hibernate session. The documentation says:
If the Session throws an exception, including any SQLException,
immediately rollback the database transaction, call Session.close()
and discard the Session instance. Certain methods of Session will not
leave the session in a consistent state. No exception thrown by
Hibernate can be treated as recoverable.
You should probably check that there is no other entity referencing the entity to delete before trying to delete it. Or simply always delete it logically, since it seems that it's what you're doing for referenced entities.

Resources