Spring Boot: Rollback transaction before request resolves - spring

I've got a method in my service class which performs some changes to an entity, afterwards it executes some critical code (communication to different microservice) which may come back with error information attached to an exception. If an error occurs, the changes should be reverted. To achieve this I thought I'd use #Transactional.
Service
#Service
public class MyService {
#Autowired
private EntityRepository repository;
#Transactional(rollbackFor = CriticalCodeException.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.NESTED)
public Entity foo(entity) {
/* perform changes ... */
entity.setBar(...)
try {
/* critical code */
} catch (CriticalCodeException e) {
e.setEntityId(entity.getId());
throw e;
}
}
public attachError(CriticalCodeException e) {
// reload entity which should be rolled back but is not
Entity reloaded = repository.findOne(e.getEntityId);
reloaded.setError(e.getError); // attach error
repository.save(reloaded) // saves changes which should be rolled back
}
}
As the CriticalCodeException is a RuntimeException and might also occur in another context, I've created a designated ExceptionHandler which is supposed to catch it and attach the error to my entity.
ExceptionHandler
#ExceptionHandler(CriticalCodeException .class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ErrorResponse processError(CriticalCodeException e) {
entityService.attachError(e);
return e.getError();
}
Yet, when I attach the error I do not wont to save the pervious made changes too. So when I reload the entity after the Exception has been thrown out of my nested #Transactional-method I'd expect it to be in its initial state. Well, it is not. If I do not attach the error (and thus omit my save() call) the changes do not get persisted. So I figure the rollback eventually executes after my exception handling.
Can anybody tell me how I can access my entity in its initial state AFTER the rollback is done?
I am using Spring Boot Data with JPA and Hibernate. I've tried various configurations with things like #EnableTransactionManagement and setting up different combinations of beans. Yet, after a lot of research, I'm pretty confindent that the #Transactional is actually working (as to be seen from the eventual rollback described above) and should work without further configuration. What am I doing wrong?
EDIT
The changes were in fact rolled back but my entity-manager did not get reset, just M. Deinum pointed out. In my persistence setup changes to an entity were only persisted by a call to repository.save(entity). What I did eventually was to omit the transactional approach and instead implement repository.reload(entity) which refreshes my entity via the entity-manager. This way I can get rid of previous made changes which did not even get persisted in the first place.

Related

guava eventbus post after transaction/commit

I am currently playing around with guava's eventbus in spring and while the general functionality is working fine so far i came across the following problem:
When a user want's to change data on a "Line" entity this is handled as usual in a backend service. In this service the data will be persisted via JPA first and after that I create a "NotificationEvent" with a reference to the changed entity. Via the EventBus I send the reference of the line to all subscribers.
public void notifyUI(String lineId) {
EventBus eventBus = getClientEventBus();
eventBus.post(new LineNotificationEvent(lineId));
}
the eventbus itself is created simply using new EventBus() in the background.
now in this case my subscribers are on the frontend side, outside of the #Transactional realm. so when I change my data, post the event and let the subscribers get all necessary updates from the database the actual transaction is not committed yet, which makes the subscribers fetch the old data.
the only quick fix i can think of is handling it asynchronously and wait for a second or two. But is there another way to post the events using guava AFTER the transaction has been committed?
I don't think guava is "aware" of spring at all, and in particular not with its "#Transactional" stuff.
So you need a creative solution here. One solution I can think about is to move this code to the place where you're sure that the transaction has finished.
One way to achieve that is using TransactionSyncrhonizationManager:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
void afterCommit(){
// do what you want to do after commit
// in this case call the notifyUI method
}
});
Note, that if the transaction fails (rolls back) the method won't be called, in this case you'll probably need afterCompletion method. See documentation
Another possible approach is refactoring your application to something like this:
#Service
public class NonTransactionalService {
#Autowired
private ExistingService existing;
public void entryPoint() {
String lineId = existing.invokeInTransaction(...);
// now you know for sure that the transaction has been committed
notifyUI(lineId);
}
}
#Service
public class ExistingService {
#Transactional
public String invokeInTransaction(...) {
// do your stuff that you've done before
}
}
One last thing I would like to mention here, is that Spring itself provides an events mechanism, that you might use instead of guava's one.
See this tutorial for example

#Transactional method call within #Transactional method (both with default propagation)

I have the following setting (it's an analogy), and the repository changes on methodB are not rollbacked. Propagations should be default = REQUIRED, so what could be the explanation?
Parent.java
#Transactional
public void methodA(){
child.methodB();
anotherMethodThatThrowsARuntimeException();
}
Child.java
#Transactional
public void methodB(){
repository.save(entity)
}
First of all, is my understanding correct in that I should expected everything rollbacked?
Even if all this situation is happening when this code is wrapped under a #Transactional(isolation = Isolation.READ_UNCOMMITTED) test?
EDIT: Just for the sake of the resolution: the problem was that.SQL rollback was indeed at the end of of outer transaction but the managed context was not cleared so rollback from inner transaction was not visible
Some database engines don't have support for transaction. First check your database engine. MyISAM engine of MySQL is an example for this case.

Spring-boot with eclipseLink transaction issue

I have implemented EntityListener in eclipseLink. My app is built using spring-boot , spring-data and eclipseLink. I have a requirement of inserting record in 2 table (Audit tables) when data in inserted in 1 table. I have got EntityManager in my Listener and everything seems to works normally. When I debug the code I can see that entities which I am going to save are having "id" generated from the sequence which is maintained at DB level. BUT when the transaction is committed I see only main table data but audit tables data is not getting committed. Here is the sample code :
#Entity
#Table(name="MyTable")
#EntityListeners(MyTableListener.class)
public class MyTable implements Serializable{
private static final long serialVersionUID = -5087505622449571373L;
private Long id;
// Rest of the fields
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="MASTER_SEQ")
#SequenceGenerator(name="MASTER_SEQ",sequenceName="MASTER_SEQ",allocationSize=1)
public Long getId() {
return id;
}
// Getters/Setters
}
Listener code :
public class MyTableListener extends DescriptorEventAdapter {
#Override
public void postInsert(DescriptorEvent event) {
MyTable msg = (MyTable)((InsertObjectQuery) event.getQuery()).getObject();
EntityManager em = BeanUtil.getBean(EntityManager.class);
MyAudit statusAudit = new MyAudit();
statusAudit.setNewVal("NEW");
statusAudit.setOldval(null);
statusAudit.setTargetColumnName(targetColumn);
statusAudit.setTargetRecordId(msg.getId());
em.persist(statusAudit);
}
}
Nothing seems to be wrong with the code. BUT when I see in the set the sql logs to "FINEST" , I can see that insert queries are not being fired for audit tables.
I have been dealing with this problem for more than 2 days and dont understand what exactly is wrong. Please help me.
You are never calling flush on the EntityManager. I suspect something like the following is going on:
You add your domain entities to the EntityManager, possibly through Spring Repositories.
Something triggers a flush, possibly the transaction handling of Spring.
Your domain entities get flushed.
Your event listener gets triggered.
You add audit entities to the EntityManager, but those never get flushed.
The database connection gets a commit. Saving everything but your audit trail.
If this theory ist correct, which you should be able to verify through the logs and debugger, you can probably fix it, by adding a call to flush in your listener.
As you described in the comments this causes further problems, which happen because you are trying to do something which you are not supposed to be doing.
According to this article, the JPA spec does not allow usage of the entitymanager inside callback events.
In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context. A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.
So looks, like we are stuck here, so you probably should consider a completely different approach like EclipseLink History tables.

Spring nested transaction marked as rollbackonly with unchecked exception

I am fairly new to Spring and transactions. I am sure this question has been asked before, but I still cannot figure the correct way to go about it.
I am using Spring and hibernate. I have a service method that goes like this:
#Transactional
public void processPendingReport(Report report) {
try {
// Do processing stuff, update report object state
reportDAO.save(report);
} catch (Exception e) {
reportDAO.markReportAsFailed(report);
}
}
If a RuntimeException occurs during processing, a "Transaction marked as rollbackOnly" RollbackException will be thrown, having as a result that the report will not be marked as failed (although I would like it to be).
I have tried using #Transactional(noRollbackFor=Exception.class), but still get the same issue.. Any suggestions? Could it be a configuration issue?
If a database exception (e.g. constraint violation) occurs in reportDAO.save() or reportDAO.markReportAsFailed() the transaction will be rolled back on the database level no matter what you are doing on the application level.
You can still mark the report as failed if reportDao.save() fails when you create a new transaction for reportDAO.markReportAsFailed(). Since ReportDAO is annotated #Transactional just remove the #Transactional annotation from the service method. You could also change the reportDAO.save() implementation to use a database function or stored procedure that wraps the insert statement and catches any exceptions on the database level.
HTH.

Another LazyInitializationException (in combination with Spring+GSON)

I guess I'm another newbie guy who fails to understand Hibernate sessions, may be Spring's TransactionTemplate, dunno. Here's my story.
I'm using Hibernate 3.5.5-Final, Spring 3.0.4.RELEASE, trying to live only with annotations (for Hibernate as well as Spring MVC).
My first try was to use #Transactional annotations in combination with properly setup transaction manager. Seemed to work at first, but in long run (about 36hours) I started to receive "LazyInitializationExceptions" over and over again (from places that were running just fine in previous hours!).
So I switched to manual transactions using Spring TransactionTemplate.
Basically I'm having something like this protected stuff in my BaseService
#Autowired
protected HibernateTransactionManager transactionManager;
protected void inTransaction(final Runnable runnable) {
TransactionTemplate transaction = new TransactionTemplate(transactionManager);
transaction.execute(new TransactionCallback<Boolean>() {
#Override
public Boolean doInTransaction(TransactionStatus status) {
try {
runnable.run();
return true;
} catch (Exception e) {
status.setRollbackOnly();
log.error("Exception in transaction.", e));
throw new RuntimeException("Exception in transaction.", e);
}
}
});
}
And using this method from the service's impls worked OK, I did not see LazyInitializationException for 10 days (running Tomcat with this single app 24*7 for 10days, no restarts) ... but than hoops! It has popped again :-/
The LazyInitializationException comes from place under "inTransaction" method and there is no "inTransaction recursion" involved, so I'm pretty sure I should be in the same transaction alas in the same Hibernate session. There is no "data from previous session" involved (as far as my code goes that service layer opens the transaction, gathers all data from Hibernate it needs, process it and returns some result == service does not recall other top-services)
I have not profiled my app (I don't even know how to do that properly in long runs such as 10 days), but my only guess is that I'm leaking memory somewhere and JVM hits heap-limit...
Is there some "SoftReferences" involed inside Spring or Hibernat? I don't know...
Another funny thing is that the exception always happen when I try to serialize the result into JSON using Google GSON serializer. I know, it does not use getters ... I have my own patched version that is using getters instead of actual fields (so I'm making sure not to bypass Hibernate proxy mechanisms), do you think it may play some role here?
Last funny thing is that the exception is always happing in the single service method (not anywhere else), which is driving me nuts because this method is as simple-stupid as it could be (no extrem DB operations, just loads data and serialize them to JSON using lazy-fetching), huh???
Does anybody have any suggestions what should I try? Do you have some similar experiences?
Thanks,
Jakub

Resources