External transaction rollback when internal transaction(REQUIRES_NEW) throw exception

ip696 :

I have method:

  @Transactional
  public void importChargesRequest() {
  ...
   for (Charge charge : charges) {

      try {
        Charge savedCharge = saveCharge(charge);
      } catch (Exception e) {
        log.error(e.getMessage());
      }
    }
}

for persist each Charge I call internal method:

@Transactional(propagation = Propagation.REQUIRES_NEW)
  public Charge saveCharge(Charge charge) {
    return chargesRepository.saveAndFlush(charge);
  }

If saveCharge methos throw exception(In my case constraint exception) I want write log, and continue persist another entities. But when I catch exception - my external transaction rolback with error:

Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit

I need open transaction and start save each entity. If some entity can not save with exception - I need log this exception and continue save another entities. When all entities will saved(or logged) I need commit external transaction. But now it rollback. How can I fix it?

EDIT:

I accepted the comments and move REQUIRES_NEW transaction to another bean:

@Service
public class TestService {

  private final TestDao testDao;

  public TestService(TestDao testDao) {
    this.testDao = testDao;
  }

  @Transactional
  public void saveTest() {
    for (int i = 0; i < 100; i++) {
      Test test = new Test();
      if (i == 10 || i == 20) {
        test.setName("123");
      } else {
        test.setName(UUID.randomUUID().toString());
      }
      testDao.save(test);
    }
  }
} 

and another bean for each internal transaction:

@Slf4j
@Component
@Repository
public class TestDao {

  @PersistenceContext
  private EntityManager entityManager;

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void save(Test test) {
    entityManager.persist(test);
  }
}

When I try save in first time I have 20 rows in DB. And each next save I get +10 rows. Name has constraint. When I get error - transaction is commit and not continue. I wait 98 rows after each save.

Buurman :

If saveCharge is a method in the same bean as importChargesRequest, the @Transactional annotation gets ignored and the saveAndFlush works in the same (outer) transaction. (I'm sure this is the case when you use proxies/interceptors to manage the transactions. I'm fairly sure its also the case when using aspectj-based transaction interception).

Normally a transaction only gets marked for rollback if an exception bubbles all the way up to the outer method (the one marked @Transaction) but I suspect the repository or the transaction manager itself (hibernate session) is directly marking the transaction for rollback due to the constraint violation.

The solution is to move the saveCharge to another bean and inject that bean into the bean with the importChargesRequest-method.

@Service
public class ChargesDataService{

  @Autowire
  private ChargesRepository chargesRepository;

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public Charge saveCharge(Charge charge) {
    return chargesRepository.saveAndFlush(charge);
  }
}

@RestController
public class ChargesController{

  @Autowire
  private ChargesDataService chargesDataService;

  @Transactional
  public void importChargesRequest() {

   for (Charge charge : charges) {

      try {
        Charge savedCharge = chargesDataService.saveCharge(charge);
      } catch (Exception e) {
        log.error(e.getMessage());
      }
    }
  }
}

Addendum: The annotation gets ignored because you no longer go through a proxy to the bean-instance, which means no interceptors get called, which means there is no place to handle the new transaction. You can check if this is the case by setting a breakpoint in the saveCharge method and looking at the stacktrace^. Look for something like a transaction interceptor invokeWithinTransaction-like method.

^ You can also create a new exception, call fillInStacktrace and then log/print the exception including its stacktrace.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=95927&siteId=1