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
Related
I have a method Service method - SampleServiceImpl().
I have declared the Service method as following:
#Transactional
#Override
public sampleDTO SampleServiceImpl(SampleDTO sampleDTO) throws SampleException, ParseException {
// Method call to methodA.
createDataA(sampleDTO);
// Method call to methodB.
createDataB(sampleDTO);
return sampleDTO ;
}
Here the DataA is created and it does not throw any exception.
But in DataB, we are trying to create DataB based on DataA. Due to some logic, we cannot create DataB. So, we throw a Sample Exception like:
count = checkIfDataBExisting(sampleDTO);
if(count == 1){
throw new SampleException(ErrorConstants.DATA_B_EXISTING);
}
But the problem is, the transaction that got committed during createDataA(sampleDTO) method call, does not get rolled Back.
Why does this don't actually work? I'm bit confused with this behavior.
EDIT:1
createData1(sampleDTO) method -
private ADTO createDataA(SampleDTO sampleDTO) throws SampleException{
ADTO aDTO = null;
try {
//CREATE NEW WORK DRIVER
aDTO = createNewDataA(sampleDTO);
//Other arbitary database transactions occurs after creation of AData.
} catch (SampleException exc) {
SampleException newException = new SampleException (exc.getExceptionObject().getExceptionCode(), exc);
throw newException ;
}
return aDTO;
}
EDIT2:
SampleException Declaration -
public class SampleException extends Exception{
//Definitions and Declarations.
}
By default declarative transactions rollback only for Runtime exceptions.
SampleException has to be RuntimeException for the Transaction to be rolled back.
Your method signature:
public sampleDTO SampleServiceImpl(SampleDTO sampleDTO) throws SampleException, ParseException
makes me think that SampleException is a checked one.
See the documentation for #Transactional:
If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on RuntimeException and Error but not on checked exceptions).
You can either make SampleException extends RuntimeException or set the rollbackFor attribute of #Transactional.
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();
}
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
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());
}
}
As shown below, I am accessing a Service layer method inside of another DAO.
(Every DAO in the system is implemented using HibernateDAOSupport class)
I wanted to rollback the transaction when #1 or #2 (commented in the following code) is failed.
But when #2 throws an exception, #1 does not get rolled back and I can see the entries in the database.
#Transactional(readOnly=false, rollbackFor={DuplicateEmailException.class,DuplicateLoginIdException.class,IdentityException.class},propagation=Propagation.REQUIRES_NEW)
public void createUserProfile(UserProfile profile)
throws DuplicateEmailException, DuplicateLoginIdException,
IdentityException {
// #1 create principal using Identity Service
identityService.createPrincipal(profile.getSecurityPrincipal());
try {
// #2 save user profile using Hibernate Template
getHibernateTemplate().save(profile);
} catch (RuntimeException e) {
throw new IdentityException("UseProfile create Error", e);
}
}
Here is the signature for createPrincipal() method of'IdentityService'.
public void createPrincipal(Principal principal) throws DuplicateEmailException,DuplicateLoginIdException,IdentityException ;
There's no Transaction management configured in 'IdentityService'
What I am doing wrong here ?
Try Propagation.REQUIRED, instead of Propagation.REQUIRES_NEW
During the calls identityService.createPrincipal(profile.getSecurityPrincipal()); aren't you flushing the session ? (executing a query for example, with FlushMode.AUTO)