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();
}
Related
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.
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 created a service method that creates user accounts. If creation fails because the given e-mail-address is already in our database, I want to send the user an e-mail saying they are already registered:
#Transactional(noRollbackFor=DuplicateEmailException.class)
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
This does not work. Although I marked the DuplicateEmailExcepetion as "no rollback", the second SQL query (findByEmail) still fails because the transaction was aborted.
What am I doing wrong?
There is no #Transactional annotation on the repository.
That's not a problem with Spring / JDBC or your code, the problem is with the underlying database. For example, when you are using the Postgres if any statement fails in a transaction all the subsequent statements will fail with current transaction is aborted.
For example executing the following statements on your Postgres:
> start a transaction
> DROP SEQUENCE BLA_BLA_BLA;
> Error while executing the query; ERROR: sequence "BLA_BLA_BLA" does not exist"
> SELECT * FROM USERS;
> ERROR: current transaction is aborted, commands ignored until end of transaction block
Still the SELECT and subsequent statements are expected to succeed against MySQL, Oracle and SQL Server
Why don't you change the logic as following :
void registerUser(User user) {
User existingUser = userRepository.findByEmail(user.getEmail())
if(existingUser == null){
userRepository.create(user);
}else{
mailService.sendAlreadyRegisteredEmail(existingUser)
}
}
This would ensure that only non-existing users to be inserted into the database.
#Transactional annotation is placed at incorrectly. Spring creates a AOP advisor around the method where #Transactional annotation is defined. So, in this case, pointcut will be created around registedUser method. But, registerUser method doesn't throw DuplicateEmailException. Hence, no rollback rules are evaluated.
You need to define the #Transactional rule around UserRepository.createUser method. This will ensure that Transaction pointcut created by spring doesn't rollback because of DuplicateEmailException.
public class UserRepository {
#Transactional(noRollbackFor=DuplicateEmailException.class)
public User createUser(){
//if user exist, throw DuplicateEmailException
}
}
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
You could wrap the call to the userRepository in a try catch block. Or you could look first if the user exists, and abourt the creation of a new one.
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());
}
}