EJB: CMT, how to retry operation after expeption thrown - java

I got session bean which is managed by containter. Recently I run into problems, where exception is thrown:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
it is because some other process has updated row (and version field has changed). Now when it is thrown, I catch OptimisticLockException and want to re-run failed operation (and I want to put WRITE lock this time to be sure it won't fail again), I do it this way:
T ctj = new T();
C ca = entityManager.find(C.class, id);
Double newBalance = Operations.add(ca.getAccountBalance(), amount);
ca.setAccountBalance(newBalance);
entityManager.persist(ca);
ctj.setBalanceAfterTransaction(newBalance);
entityManager.persist(ctj);
try {
flushRegisterTransactionUpdateAccountBalance();
} catch(OptimisticLockException ex) {
retryBalanceUpdate(ca, ctj, amount);
}
and the methods I call in above:
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void retryBalanceUpdate(C ca, T ctj, Double amount) {
entityManager.refresh(ca);
entityManager.lock(ca, LockModeType.WRITE);
Double newBalance = Operations.add(ca.getAccountBalance(), amount);
ca.setAccountBalance(newBalance);
entityManager.persist(ca);
ctj.setBalanceAfterTransaction(newBalance);
entityManager.persist(ctj);
flushRegisterTransactionUpdateAccountBalance();
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void flushRegisterTransactionUpdateAccountBalance() {
entityManager.flush();
}
Those 2 methods I have crated, because I was hoping the whole (parent) transaction will not fail because of exception thrown by flushRegisterTransactionUpdateAccountBalance().
Unfortunately it fails, when I call in catch block method retryBalanceUpdate, the first line of it's body (entityManager.refresh(ca)) throws:
[TxPolicy] javax.ejb.EJBTransactionRolledbackException: EntityManager must be access within a transaction
[MyBean] No transaction
javax.persistence.TransactionRequiredException: EntityManager must be access within a transaction
Does anybode know how I could achieve what I explained? I am using EJB 3.0, entityManager object is initiated by class-level annotation:
#PersistenceContext(unitName="MyPersistenceUnit") private EntityManager entityManager;
The class it self is stateless session bean with transaction attribute SUPPORTS

OptimisticLockException :
Thrown by the persistence provider when an optimistic locking conflict occurs. This exception may be thrown as
part of an API call, a flush or at commit time. The current
transaction, if one is active, will be marked for rollback.
You can create a custom exception with annotation #ApplicationException(rollback=false). In flushRegisterTransactionUpdateAccountBalance you have to catch OptimisticLockException & re-throw custom exception.
Can refer the below the sample code.
try {
flushRegisterTransactionUpdateAccountBalance();
} catch(XApplicationException ex) {
retryBalanceUpdate(ca, ctj, amount);
}
In below code, handling the exception & then re-throwing custom exception which is marked as rollback = false.
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void flushRegisterTransactionUpdateAccountBalance() throws XApplicationException{
try {
entityManager.flush();
} catch(OptimisticLockException ex) {
throw new XApplicationException(ex.getMessage());
}
}

Related

Spring Catch JpaSystemException in #Transactional Method and Roll Back Transaction

I have a method that is annotated with
#Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
and calls several repository methods. Now when one repository tries to alter a DB-row that is locked and not rolled back by another instance of the method, spring correctly throws
org.springframework.orm.jpa.JpaSystemException: could not execute statement [...] Caused by: java.sql.SQLException: transaction could not be serialized and rolls back the failed transaction.
Now I want to keep all this behaviour but additionally handle the exception and start a retry. Here's a code snippet:
#Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public void messageReceived(Card card) {
this.request = request;
this.numberOfAttempts = reset ? 0 : this.numberOfAttempts++;
this.reset = true;
LOGGER.info("Message received from Queue: " + card);
TransactionDebugUtils.transactionRequired("MessageReceivedController.messageReceived");
try {
[...]
repository1.createKonto(card);
repository2.doStuff(card);
} catch (JpaSystemException e) {
//This is obviously never invoked
LOGGER.error("TRANSACTION FAILED!!!");
} catch (Exception e) {
LOGGER.error("Error mapping json request to data model", message, e);
}
}
#ExceptionHandler(JpaSystemException.class)
//This is also never invoked
public void handleJpaSystemException(JpaSystemException ex) {
this.messageReceived(this.request);
this.reset = false;
}
I had this issue recently. As it is a method level #Transactional annotation, Transaction commit occurs after finishing method execution.
When you are using this annotation 2 concepts should be considered
persistence context
database transaction
After messageReceived() method is executed, those 2 things will happen and JPA exception is thrown at #Transactional level which means you need to handle this exception from where you are calling this method(controller; if you are calling from a controller).
More regarding #Transactional can be found in this link.

Hibernate transaction committed in case of exception encountered

I had created a service layer in which my method was having the transactional annotation over it in the following manner :
#Transactional
void a() {
User user = new User(1, "Abc", "Delhi");
userDao.save(user);
A a = null;
a.toString(); //null pointer exception being encountered here.
}
The transaction should have been rolled back and the user's details should not have been persisted to the db, but it is not happening.
Run time exceptions will roll back the transaction by default. I don't know exactly in hibernate, but in eclipse link implementation of JPA, we can specify the rollback = true/false for the application exceptions as shown below.
#ApplicationException(inherited = true, rollback = true)
try similar configuration change.
you can also rollback in the catch block something like below
catch(Exception e) {
entityManger.getTransaction().rollback();
}

#Transactional not rolling back

One #Transactional method calling to another 2 methods which are also present in #Transactional method but while one of the called method getting exception it the transaction should be rolled back , its not happening
-----The Main Transactional method-------------
#Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RestException.class)
public BaseDto createFPSAndUser(FpsStoreDto fpsStoreDto){
log.info("<--Starts FPSStoreService .createFPSAndUser-->"+fpsStoreDto);
BaseDto baseDto = new BaseDto();
try {
UserDetailDto userDetailDto = fpsStoreDto.getUserDetailDto();
userDetailDto.setCreatedBy(fpsStoreDto.getCreatedBy());
baseDto = createFPSStore(fpsStoreDto);
if(baseDto.getStatusCode() != 0){
throw new RestException(ErrorCodeDescription.getDescription(baseDto.getStatusCode()));
}
userDetailDto.setFpsStore(null);
baseDto = userDetailService.createUserDetail(userDetailDto);
if(baseDto.getStatusCode() != 0){
throw new RestException(ErrorCodeDescription.getDescription(baseDto.getStatusCode()));
}
FPSStore fpsStore = fpsStoreRepository.findByCode(fpsStoreDto.getCode());
UserDetail userDetail = userDetailRepository.findByUserId(userDetailDto.getUserId());
userDetail.setFpsStore(fpsStore);
userDetailRepository.save(userDetail);
baseDto.setStatusCode(0);
} catch(RestException restException){
log.info("RestException -:", restException);
restException.printStackTrace();
baseDto.setStatusCode(baseDto.getStatusCode());
} catch (Exception exception) {
log.info("Exception -:",exception);
exception.printStackTrace();
baseDto.setStatusCode(ErrorCodeDescription.ERROR_GENERIC.getErrorCode());
}
log.info("<--Ends FPSStoreService .createFPSAndUser-->"+baseDto);
return baseDto;
}
------------------Called method 1st-----------
#Transactional(propagation = Propagation.REQUIRED)
public BaseDto createFPSStore(FpsStoreDto fpsStoreDto) {
_________________________
__________________________
________________________
return baseDto;
}
------------------------2nd Transactional method-----
#Transactional(propagation = Propagation.REQUIRED)
public BaseDto createUserDetail(UserDetailDto userDetaildto) {
_______________
_______________
_______________
return baseDto
}
You've set rollbackFor=RestException.class, yet your code catches that very exception and doesn't rethrow it. From Spring's point of view RestException was never thrown by the method, and there's no reason to rollback the transaction.
If you want the rollback to happen, you need to do throw restException; in the end of your catch block.
You are telling Spring that rollback the transaction only when
rollbackFor = RestException.class
But if you catch the excecption
catch(RestException restException){
Spring will never get notice that an exception was thrown. You need to remove your catches blockes (both of them) or you can throw the exception at the end of your catch
catch(RestException restException){
log.info("RestException -:", restException);
restException.printStackTrace();
baseDto.setStatusCode(baseDto.getStatusCode());
throw restException;
}
#Transactional tell the container (spring) to handle the transaction management for the annotated method call.
This is done using proxy, see understanding aop proxies which means that :
Annotation is considered only for call external to the annotated object
Only exception thrown outside of the method boundaries will be handled by the container
`
each annotated method has its own transaction logical context which means that :
Even if your main method has the parameter rollbackFor=RestException.class
the inner ones won't inherit the configuration and won't trigger a rollback when throwing a rest exception
If an inner method trigger a rollback due to an exception thrown during it's execution, the transaction will be rolled back even if the exception is caught by the caller, every subsequent access to the database will result in an UnexpectedRollbackException

How to avoid automatic entity update on Hibernate ValidationException

I having a spring method: where I am validating the entity after constructing of object, which was previously fetched from DB.
#Transactional(rollbackFor={ValidationException.class})
public Object approve(Command command) throws ValidationException {
Object obj = merger.mergeWithExistingobject(command); // where I am fetching object from DB with old values and construct the new object
validatorService.validate( obj ); // which throws ValidationException
return saveObject(obj);
}
But unfortunately even after the ValidationException was thrown. The values still get persisted in DB. How can I avoid this situtation.
You can evict the entity on ValidationException:
try {
validatorService.validate( obj );
} catch (ValidationException e) {
entityManager.detach(obj);
//Or with Hibernate API
//session.evict(obj);
throw e;
}
I've never used spring but if this is the same as EJB then transactions are only rolled back when unchecked exceptions are thrown.
When a java.lang.Exception is thrown from the EJB method, you obviously have to declare it in the method's signature with thethrows keyword. What happenes in this case as follows 1) the transaction gets commited 2) the exception is rethrown to the client
Taken from this link

Current session get invalidate after catching exception but i have to make more query

I try to execute this code
#Transactional
#Controller
public class MyController{
....
#RequestMapping(..)
public String MyMethod(...)
{
....
try {
ao_history_repository.save(new AoHistory(..));
}
catch (DataIntegrityViolationException e) {
System.out.println("history already exist");
}
....
model.addAttribute("...", my_respository.findAoToDetail(id) );
return "...";
}
But when i got duplicate entry Exception i catch it but after i got a other Exception
org.hibernate.AssertionFailure: null id in persistence.AoHistory entry
(don't flush the Session after an exception occurs)
I know that When a ConstraintViolationException is thrown it invalidates the current session but how can i reopen a new session and a new transaction ?
As you write, you need a new transaction. From your code snippet it looks like the simplest thing would be to move #Transactional from the controller to the repository classes. As an alternative, you could add a service layer and move #Transactional there.
A different approach would be to pre-check the entity object before trying to save it in the entity manager, so that exception is never thrown.

Categories

Resources