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
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 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.
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.
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());
}
}
Below is what I did, I need to implement rollback, using #transactional annotation, but not working as expected, what else need to be done for proper rollback to happen ?? I want that when the code is executed result in db should be "testingOne" , currently it is set to "notRollBacked". Can you please point my mistake.
public Response deleteUser(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
testRollbackFunction();
}
catch (Exception ex)
{
}
return new Response();
}
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
private void testRollbackFunction() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
****************Edit 1*************
I did what was mentioned in answers:
I did taken bean from context
and written a class/bean as
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public class RollBack
{
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public void thisWillRollBack() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
}
spring xml file bean refrence set as :
<bean id="rollBackBean" class="com.alepo.RollBack">
</bean>
public Response myMethod(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
Test test = new Test();
Object obj = test.getBean();
RollBack rollBack = (RollBack)obj;
rollBack.thisWillRollBack();
}
catch (Exception ex)
{
ex.printStackTrace();
}
return new Response();
}
#################EDIT 4
now calling rollback function as :
RollBack rollBack = (RollBack)PortalBeanLocatorUtil.getBeanLocator().locate("rollBackBean");
rollBack.thisWillRollBack();
No Test class in picture now ...no new anywhere ...
still NOT WORKING .......
If you have a #Transactional annotation on method, Spring wraps the call to this method with aspect handling the transaction.
So:
1) Only public methodes can be wrapped in aspect
2) You call wrapped code only if you call the method on a bean taken from / injected by Spring container.
In your case:
1) The code isn't wrapped in transactional aspect because it is not public method
2) Event if it was, it is called directly from within the class, so you wouldn't call wrapped version anyway.
So the solution is to make separate bean with #Transactional method, inject it into and call it from Response class.
Of course you need <tx:annotation-driven/> in your spring-xml or instruct Spring otherwise to process #Transactional annotations (see the reference).
The issue is you are outside the application context. You are creating a new instance of a class, NEW is bad in Spring, very bad. Get an instance of Test from the application context, not by creating a new instance unless you start your application context in Test. Try to Autowire test in your class you mention above or inject it from Spring and then let me know, but the code you are showing above will never work with transaction management.