Beez
Beez

Reputation: 532

Calling @Transactional Method from original @Transactional method causing rollback issue in same class

I currently have a use case, where I where if my user manually inserts a data file to be read into the database I need to check if the data exists in the DB. If it does, I want to delete it and then process and save the new file. The problem with this is my methods are marked @Transactional so even though the delete methods are ran, they aren't committed before the save method is called which violates a unique constraint casuing the rollback.

I have tried every level of propagation and also tried splitting them up into two separate transactions where my controller calls them one by one and they don't call each other.

ERROR: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

CODE:

 @Transactional
  public void saveAllPositionData(InputStream is) throws IOException {

    log.info("Parsing position data...");
    ParsingResult parsingResult = positionParser.parse(is);
    if (!parsingResult.getPositions().isEmpty()) {
      LocalDate businessDate = parsingResult.getPositions().get(0).getBusinessDate();
      overwriteData(businessDate);
    }

    try {
      positionRepo.saveAll(bpsParsingResult.getPositions()); // UNIQUE CONSTRAINT FAILS HERE CAUSING ROLLBACK
      priceRepo.saveAll(parsingResult.getPrices());

      for (PositionTable position : parsingResult.getPositions()) {
        if (position.getNumberOfMemos() > 0) memoRepo.saveAll(position.getCorrespondingMemos());
      }
    } catch (Exception e) {
      log.warn("Invalid data returned from BPS parsing job: {}", e.getMessage());
    }
  }

  @Transactional(propagation = Propagation.NESTED) // Tried Propagation.* and no Annotation
  public void overwriteData(LocalDate businessDate) {
    if (memoRepo.countByBusinessDate(businessDate) > 0) {
      log.warn(
              "Memo record(s) found by {} business date. Existing data will be overridden.",
              businessDate);
      memoRepo.deleteByBusinessDate(businessDate);
    }
    if (positionRepo.countByBusinessDate(businessDate) > 0) {
      log.warn(
          "Position record(s) found by {} business date. Existing data will be overridden.",
          businessDate);
      positionRepo.deleteByBusinessDate(businessDate);
    }
    if (priceRepo.countByBusinessDate(businessDate) > 0) {
      log.warn(
          "Price record(s) found by {} business date. Existing data will be overridden.",
          businessDate);
      priceRepo.deleteByBusinessDate(businessDate);
    }
  }

Upvotes: 2

Views: 1147

Answers (2)

Jalena
Jalena

Reputation: 129

Because you are calling overwriteData() (@Transactional) from the transactional method saveAllPositionData() a new transaction was not created but it was executed in the same transaction. This means just like you said

even though the delete methods are run, they aren't committed before the save method is called which violates a unique constraint causing the rollback.

The following illustrates the above situation, where UserService is a class and invoice is a transactional method which the createPDF inner method which is also transactional.

transactional calsss proxy

Spring creates that transactional UserService proxy for you, but once you are inside the UserService class and call other inner methods, there is no more proxy involved. This means, no new transaction for you.

One way to get around this is self-injection or here

Another is to keep both the methods in different class.

Upvotes: 1

Ken Chan
Ken Chan

Reputation: 90567

UnexpectedRollbackException usually happens when an inner @Transactional method throws exception but the outer @Transactional method catch this exception but not re-throw it.(See this for more details). Methods on the JpaRepository actually has @Transactional annotated on it. Now in saveAllPositionData() , some method calls on the JpaRepository throw exception but you catch it and not rethrow it so it causes UnexpectedRollbackException.

Also , @Transactional method does not work if you self calling it from the inner class. That means @Transactional on overwriteData() does not have effect in your codes. (See Method visibility and @Transactional section in docs for more detail)

The problem with this is my methods are marked @Transactional so even though the delete methods are ran, they aren't committed before the save method is called which violates a unique constraint casuing the rollback

You can try to call flush() on the JpaRepository after calling the delete method. It will apply all the pending SQL changes collected so far to the DB but will not commit the transaction. So only the transaction involved will see the records are deleted such that when you insert the data in the same transaction later , you should not encounter unique constraint violation .

Upvotes: 2

Related Questions