Hibernate/Spring - Rollback a transaction within a transaction - java

Given this example code:
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Here is what I want to achieve:
Only processItem(anItem); should rollback if exception occurs inside it.
If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.
Is there a solution that doesn't involve managing transactions manually (without annotations)?.
Edit: I was thinking of using #Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.

This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.
Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by #Transactional.

It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
// If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional(PROPAGATION = PROPAGATION.NESTED)
// Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.

Related

Transactional not rolling back JPA save in spring boot

I am working on a spring boot app where I am using transactional and its not rolling back its changes when I throw a exception:
My method:
private BtoBWalletTransactionResponseModel doWalletOperation(BtoBWalletTransactionTypes transactionType, BtoBWalletTransactionRequestModel transactionRequest) {
// DB Operation
BtoBWalletTransaction savedTransaction = commonTransactionalService.finishWalletTransaction(userWallet, btoBWalletTransaction);
log.info("wallet {} txn of amount {} for user {}",transactionType.name(),txnAmount,userId);
// throwing a exception to rollback
throw new RuntimeException("Time to Rollback");
} catch(Exception e){
log.error(e.getMessage());
log.error("error while doing wallet operations for user {}",userId);
throw new WalletException(e.getMessage());
}
}
My common TransactionalService Interface:
public interface CommonTransactionalService {
BtoBWalletTransaction finishWalletTransaction(BtoBUserWallet userWallet,BtoBWalletTransaction btoBWalletTransaction);
}
My Interface Impl:
import javax.transaction.Transactional;
#Service
public class CommonTransactionalServiceImpl implements CommonTransactionalService {
#Autowired
private BtoBWalletTransactionRepo btoBWalletTransactionRepo;
#Transactional
#Override
public BtoBWalletTransaction finishWalletTransaction(BtoBUserWallet userWallet, BtoBWalletTransaction walletTransaction) {
BtoBWalletTransaction savedTransaction = btoBWalletTransactionRepo.save(walletTransaction);
btoBUserWalletRepo.save(userWallet);
return savedTransaction;
}
}
Now even when I am sending a RuntimeException the DB record is not getting rolled back.
Can someone help? stuck since hours here.
Transactional is scoped, if you anotate a method (or class) as #Transactional all the methods this class calls wil also be transactional. and if within this transaction an exception occurs things wil be rolled back.
If however like in you example a non-transactional method calls a transactional one and after that call throws an exception the previous transactioned function wil not be rolled back as it's outside of the transactions scope.

UnexpectedRollBackException in Spring inner transaction

I have two classes:
#Service
#Transaction
class A {
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
#Service
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void method2(){
save2()
throw new SqlException();
}
}
I got an SqlException as expected, but also an UnexpectedRollBackException, and the program stops.
I want to know why the data persisted by save2() is not rolled back?
Is it a problem with outer transaction?
UPDATE: I tried catching UnexpectedRollBackException in class A and everything works fine. But I still need some kind of explanation why I get the exception? I suppose the outer transaction should be suspended when the inner transaction begins, so why the rollback is unexpected for the outer transaction?
Thanks.
First of all: you are not injecting the instance of B into class A via spring. i.e. your b is not managed by spring => this leads to the behaviour: Spring is ignoring the #Transactional annotation on method.
Second if you don't want to rollback on SqlException you have to specify noRollbackFor=SqlException.class
UPDATE: after clarification, the call is happening on managed bean. But the problem that expected behaviour - transaction inside of transaction is not supported in general by the transaction management system out of the box. https://docs.spring.io/spring/docs/4.3.11.RELEASE/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRES_NEW
Unless the details on that system are provided it is impossible to step forward. Out of content the NESTED transaction propagation could be better, then REQUIRES_NEW, because it is making a rollback point for the external transaction and starts new one.
I have a simliar scenario like this and resolved it using propagation = Propagation.NESTED from where the second method is calling. rewrite the code as mentioned below and try.
#Service
class A {
#Transactional(rollbackFor = { HibernateException.class}, propagation = Propagation.NESTED)
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
Note: Transactional is used in method level for class A with propagation nested . Class B shown below.
#Service
class B {
#Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRED)
public void method2(){
save2()
throw new SqlException();
}
}
This has resolved the issue in my case.

Best practices with EJB [duplicate]

This question already has answers here:
Letting the presentation layer (JSF) handle business exceptions from service layer (EJB)
(1 answer)
Handling service layer exception in Java EE frontend method
(3 answers)
Closed 5 years ago.
Recently, I've decided to use EJB in my project. Basically, are Stateless EJBs in order to make the business layer and persist data. I'm also using container managed transactions (CMT) and everything works fine.
I tried to handle the exceptions in EJB service methods in order to get the more specific exceptions and give specific errors messages to the client.
#Stateless
public class EJBService {
#PersistenceContext(unitName="PU")
private EntityManager em;
public void save(Animal a) throws AppException
{
try
{
em.persist(e);
}
catch(ConstraintViolationException ex)
{
throw new AppException("message");
}
catch(Exception ex)
{
throw new AppException("message 1");
}
}
}
The AppException extends Exception and is marked with the annotation #ApplicationException(rollback=true)
However, I can't handle the exception before the method commit the transaction. If something goes wrong the transactions are not commited (OK), but I don't have the specific exception, only javax.ejb.EJBTransactionRolledbackException: Transaction rolled back. I could use the flush method after try to persist or merge, but it isn't seems right.
So, I've decided don't try to handle the exceptions in EJB service layer leaving my methods like this:
#Stateless
public class EJBService {
#PersistenceContext(unitName="PU")
private EntityManager em;
public void save(Animal a)
{
em.persist(a);
}
and the client (A managed bean, for example), like this:
#Named
#RequestScoped
public class Bean {
private Animal animal;
#Inject
private EJBService service;
public void save() {
try
{
service.save(animal);
}
catch(Exception e)
{
//Unwrapping the exception in order to pass the
//appropriate message
}
}
}
In order to show the right messages, the method who invoked the ejb service must to handle the exception by itself, unwrapping the exception.
The question is: Am I right ? If I'm not, what is the best way to handle exceptions ?

Spring : Caught EmptyResultDataAccessException causing entire transaction to rollback

I have two methods are shown below.
#Transactional
public void methodA(){
logger.trace("Executing methodA");
methodB()
logger.trace("Executing methodA completed");
}
public void methodB(){
//other codes here
try{
staffDao.queryById(1) //Fetch a record from database
}catch(EmptyResultDataAccessException e){
logger.trace("Staff does not exists")
}
//other codes here
}
When there occurs an EmptyResultDataAccessException within methodB()
, the entire transaction started on methodA() is rollbacked, by below exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
I know this is the default behaviour of spring #Transactional annotation.
For my case, I need to commit the transaction even when there is an EmptyResultDataAccessException. As EmptyResultDataAccessException is a RuntimeException, I can't use the noRollBackFor attribute of #Transactional annotation.
Can anyone suggest a solution ?
I have not looked closely at your code yet but if you just need a way to not rollback the transaction for a particular exception, you can mark that in #Transactional annotation.
#Transactional(noRollbackFor = {EmptyResultDataAccessException.class})
public void methodA(){
.
.
}
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
Looking at the code of deleteById in SimpleJpaRepository
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
I thought this is just a convenience method. If I don't want the exception I just implement it the way I need it:
repo.findById(id).ifPresent(repo::delete);

What happen on Transaction.rollback in nested session/transactions?

Consider this two classes: EmployeeDetailDAOImpl and EmployeeDAOImpl. Assume if I want to create a new employee, I should also create a new record for EmployeeDetail.
Given the below implementation, I wonder if the outer transaction(EmployeeDAOImpl's tx) is rolled back due to any exceptions happened after the detailDAO.create(employeeId) call, will the transaction of new EmployeeDetail be rolled back as well?
public class SessionHandler {
public static getSession() {
return Configuration.buildSessionFactory().openSession(); //ignore the isConnected or other exception handling for now
}
}
public class EmployeeDetailDAOImpl {
public void create(Serializable employeeId) {
Session session = SessionHandler().getSession();
Transaction tx = session.beginTransaction();
try {
EmployeeDetail detail = new EmployeeDetail(employeeId);
session.save(detail );
} catch (Exception e) {
if (tx!= null) {
tx.rollback;
}
}
session.close();
}
}
public class EmployeeDAOImpl {
public void add(String name) {
Session session = SessionHandler().getSession();
Transaction tx = session.beginTransaction();
try {
Employee employee = new Employee(name);
Serializable employeeId= session.save(employee);
EmployeeDetailDAOImpl detailDAO = new EmployeeDetailDAOImpl();
detailDAO.create(employeeId);
//more things here, that may through exceptions.
} catch (Exception e) {
if (tx!= null) {
tx.rollback;
}
}
session.close();
}
}
Actually, none of the given answers is 100% correct.
It depends on the calling party/service.
If you are calling the methods from an EJB, you will have 1 transaction covering both method calls. That way, the transaction will roll back both operations in case of an exception. Reason behind this is that every method in EJB is transaction Required, unless specified otherwise in the annotation or ejb deployment descriptor.
If you are using spring or any other DI framework, then it depends on your configuration. In a normal setup, your calling transaction will be suspended, since the JPA EJB will create its own transaction. You can however use the JTATransactionManager (As specified here) to make sure that both your EJB and your Spring bean share the same transaction.
If you call the JPA methods from a POJO, then you will have to take care of the JTA transaction handling yourself.
Yes, it will rollback the entity Employee as well. It doesn't even depend on whether the other entities are related.
It depends on the scope of the transaction, which here includes both Employee and EmployeeDetails
You are creating two different transaction for each method.Hence rollback can not happen.
To rollback the transaction you require the propogation in Transaction.
You need to write the code like below::
#Transactional(propagation=Propagation.REQUIRED)
public void testRequired(User user) {
testDAO.insertUser(user);
try{
innerBean.testRequired();
} catch(RuntimeException e){
// handle exception
}
}
Below is link for more information of Propogation.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/transaction/annotation/Propagation.html
http://www.byteslounge.com/tutorials/spring-transaction-propagation-tutorial

Categories