I've seen several different posts regarding this topic, but most of them tend to be that it's not working. What I'm trying to do is save several objects in a loop, but if one of them fails -> rollback all of the saved objects.
Here is my current code.
#Override
public Fleet saveFleet(String fleetId, List<String> serialNoList) {
fleet = new Fleet();
Fleet tempFleet = new Fleet();
fleet.setKey(new FleetKey());
//Change this to string utils uppercase
fleet.getKey().setFleetId(StringUtils.upperCase(fleetId));
fleet.getKey().setUserId(StringUtils.upperCase(userService.getCurrentUser().getUserId()));
fleet.getKey().setDealerCd("USER");
for (int i = 0; i < serialNoList.size(); i++) {
//Try catch block?
tempFleet = fleetRepo.save(fleet);
}
//commit if all the data goes correctly, rollback if there is an exception.
return tempFleet;
}
Add #Transactional(rollbackFor = Exception.class) to the top of the method. Spring will rollback all data within this transaction for you if any exception is thrown by database.
As others have mentioned you can use #Transactional:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html
What it actually does is:
try {
transaction.begin();
saveFleet(fleetId, serialNoList);
transaction.commit();
} catch(Exception ex) {
transaction.rollback();
throw ex;
}
You can use #Transactional annotation to rollback transaction in case of exception.
Related
Bank.java
#Stateless
#Local
public class Bank implements IBank {
#EJB
IConfigBean iConfigBean;
#EJB
IDbs iDBS;
#EJB
IPosb iPosb;
#Override
public void doTransaction() {
System.out.println("--Bank Transaction Started--");
try {
Config config1 = getConfig(1);
iConfigBean.create(config1);
iDBS.doDBSTransaction();
Config config3 = getConfig(3);
iConfigBean.create(config3);
iPosb.doPOSBTransaction();
Config config5 = getConfig(5);
iConfigBean.create(config5);
} catch (Exception e) {
e.printStackTrace();
System.out.println("---Bank Exception--");
}
System.out.println("--Bank Transaction End--");
}
#Override
public Config getConfig(int inserttionOrderNo) {
Config config = new Config();
config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
return config;
}
}
DBS.java
#Stateless
#Local
public class DBS implements IDbs {
#EJB
IConfigBean iConfigBean;
#Override
public void doDBSTransaction() {
System.out.println("--DBS Transaction Started--");
try {
Config config2 = getConfig(2);
iConfigBean.create(config2);
} catch (Exception e) {
e.printStackTrace();
System.out.println("--DBS Exception--");
}
System.out.println("--DBS Transaction End--");
}
#Override
public Config getConfig(int inserttionOrderNo) {
Config config = new Config();
config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
return config;
}
}
POSB.java
#Stateless
#Local
public class POSB implements IPosb {
#EJB
IConfigBean iConfigBean;
#Override
public void doPOSBTransaction() {
System.out.println("--POSB Transaction Started--");
try {
Config config4 = getConfig(4);
iConfigBean.create(config4);
if (true) {
//For Test 1
//throw new NullPointerException();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("--POSB Exception--");
}
if (true) {
//For Test 2
// throw new NullPointerException();
}
System.out.println("--POSB Transaction End--");
}
#Override
public Config getConfig(int inserttionOrderNo) {
Config config = new Config();
config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
return config;
}
}
I am new to Stack Overflow and Its my new question so correct me If I am wrong.
Environment is..
Windows 10
Java 1.8
Eclipse
Tomcat 8.5
EJB3
I have Three stateless bean, Please look at the Sequence Diagram of the Transaction flow.
I purposely making NullPointer Exception at two places during the transaction to know the difference and I have marked with Lightening Bold symbol in sequence diagram.
I am not using any #TransactionAttribute to any methods.
Test 1 - Null Pointer in Inside the try block (Lightening Bold symbol with Green)
When I start the testing, Got Null pointer exception and all the transaction are not marked for roll back and data also got inserted in db.
I can only see Null pointer exception in the console log.
Test 2 - Null Pointer in Outside the try - catch method (Lightening Bold symbol with Red)
When I start the testing, Got Null pointer exception plus EJBTransactionRolledbackException and all the transaction marked for roll back and no data inserted in db.
I can see NullPointer and EJBTransactionRolledback Exception in the console log.
Question here is,
Why EJB transaction is not marked for roll back If I made Null pointer inside try block
Why EJB transaction is roll back happens If I made null pointer outside try block
Thanks in advance.
Keep in mind EJB calls, all the "magic" made by container happens there, including transaction markup. It's possible due to the fact that EJB calls are not direct, but always go through proxy.
You have such calls in your code:
iPosb.doPOSBTransaction();
So, if unchecked exception (NPE for example) is thrown in this method and not caught - it's ultimately caught by container due to EJB proxy wrapping the call above. In this case transaction only could be rolled back.
Adding a call to a method of the same bean in your method (without using #EJB reference), does not change that:
#Override
public void doPOSBTransaction() {
try {
Config config4 = getConfig(4);
iConfigBean.create(config4);
if (true) {
newMethod();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("--POSB Exception--");
}
if (true) {
newMethod();
}
}
private void newMethod(){
throw new RuntimeException();
}
You can easily check that commit/rollback behaviour is just the same in this case, no matter that a method is added to call stack.
So the important thing you have to remember is that all container tricks work only on #EJB calls. So, for example, it's pointless to place transactional annotation on a private method - it won't be used ever.
Another important point is about checked exceptions. By default these do not cause transaction rollback indeed. But it's still possible to annotate your checked exception like below to make it rollback ongoing transaction:
#ApplicationException(rollback = true)
I am getting pesimistlockexception when trying to persist multiple object of same time through JPA.
Here is my code for reference
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Boolean changeDplListMappingByCustomWatchList(List<Integer> dplIds, Integer customWatchListId,
ServiceRequestor customServiceRequestor) {
for(Integer dplId : dplIds) {
if(dplId != null) {
CustomWatchListDplMapping customWatchListDplMapping = new CustomWatchListDplMapping();
customWatchListDplMapping.setDplId(dplId);
customWatchListDplMapping.setWatchListId(customWatchListId);
this.create(customWatchListDplMapping);
}
}
}
catch(Exception e) {
LOG.error("Exception occured while changing dpl mapping by custom watchList id", e);
}
return true;
}
public void create(Model entity) {
manager.persist(entity);
manager.joinTransaction();
}
After first entity when it iterate through second one it throws an exception. If it has only one entity to save then it works well, but for more than one entity model it throws this exception.
by default pessimistic lock is for 1 second so please do the changes in the properties file it will help you to unlock and you will be able to save into database
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 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