Try To Use `savepoint` In Spring JPA/Hibernate

George Chou
Javarevisited
Published in
2 min readFeb 26, 2024
savepoint

This article describes my failed attempt to use the PostgreSQL SAVEPOINT in Spring JPA.
About PostgreSQL SAVEPOINT.

EntityManager

@PersistenceContext  
private EntityManager entityManager;

@Transactional(propagation=NESTED, isolation= READ_UNCOMMITTED)
public void transferWithSavePoint(String fromAccount, String toAccount, String anotherAccount,
double amount) throws SQLException {
// step 1
Account from = accountRepository.findByName(fromAccount);
Branch fromBranch = branchRepository.findByName(from.getBranchName());

from.setBalance(from.getBalance().subtract(BigDecimal.valueOf(amount)));
fromBranch.setBalance(fromBranch.getBalance().subtract(BigDecimal.valueOf(amount)));

accountRepository.save(from);
branchRepository.save(fromBranch);

Connection connection = entityManager.unwrap(SessionImpl.class).connection();
Savepoint savepoint = connection.setSavepoint();

// step 2
Account to = accountRepository.findByName(toAccount);
Branch toBranch = branchRepository.findByName(to.getBranchName());

to.setBalance(to.getBalance().add(BigDecimal.valueOf(amount)));
toBranch.setBalance(toBranch.getBalance().add(BigDecimal.valueOf(amount)));

accountRepository.save(to);
branchRepository.save(toBranch);

connection.rollback(savepoint);

// final step
Account finalAccount = accountRepository.findByName(anotherAccount);
Branch finalBranch = branchRepository.findByName(to.getBranchName());

finalAccount.setBalance(finalAccount.getBalance().add(BigDecimal.valueOf(amount)));
finalBranch.setBalance(finalBranch.getBalance().add(BigDecimal.valueOf(amount)));

accountRepository.save(finalAccount);
branchRepository.save(finalBranch);
}

I use Connection Pool in this project, so the connection created by EntityManager is different @Transactional method’s connection

  • savepoint: is before transferWithSavePoint
  • rollback: rollback to all of the operations before, so `STEP1` is rollback, but `STEP2` and `Final STEP` is done.

Next Try: AbstractTransactionStatus

@Autowired  
private JdbcTemplate jdbcTemplate;

@Autowired
private TransactionTemplate transactionTemplate;

public void transferWithSavePoint(String fromAccount, String toAccount, String anotherAccount,
double amount) {
transactionTemplate.execute(status -> {
Account from = accountRepository.findByName(fromAccount);
Branch fromBranch = branchRepository.findByName(from.getBranchName());

from.setBalance(from.getBalance().subtract(BigDecimal.valueOf(amount)));
fromBranch.setBalance(fromBranch.getBalance().subtract(BigDecimal.valueOf(amount)));

accountRepository.save(from);
branchRepository.save(fromBranch);

Object savepoint = status.createSavepoint();

// step 2
Account to = accountRepository.findByName(toAccount);
Branch toBranch = branchRepository.findByName(to.getBranchName());

to.setBalance(to.getBalance().add(BigDecimal.valueOf(amount)));
toBranch.setBalance(toBranch.getBalance().add(BigDecimal.valueOf(amount)));

accountRepository.save(to);
branchRepository.save(toBranch);

status.rollbackToSavepoint(savepoint);

// final step
Account finalAccount = accountRepository.findByName(anotherAccount);
Branch finalBranch = branchRepository.findByName(to.getBranchName());

finalAccount.setBalance(finalAccount.getBalance().add(BigDecimal.valueOf(amount)));
finalBranch.setBalance(finalBranch.getBalance().add(BigDecimal.valueOf(amount)));

accountRepository.save(finalAccount);
branchRepository.save(finalBranch);

status.releaseSavepoint(savepoint);

return null; });
}

Also failed:

JpaDialect does not support savepoints — check your JPA provider’s capabilities

Then I start to search ‘How to use savepoint in Hibernate’ online:

Please note that this is not a recommended way to use Spring JPA. It’s better to structure your transactions in such a way that you don’t need to use savepoints or nested transactions. If you find yourself needing them, it might be a sign that your transactions are too complex and should be broken down into smaller parts.

Maybe I’m wrong from the beginning, indeed, I won’t encounter it in real application scenarios, but I’m just trying to figure out how to implement it, so let me know if you have any good ideas.

--

--