DEV Community

georgechou
georgechou

Posted on

1

Try To Use `savepoint` In Spring JPA/Hibernate

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);  
}
Enter fullscreen mode Exit fullscreen mode

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

Image description

  • 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;    });  
}
Enter fullscreen mode Exit fullscreen mode

Also failed:

JpaDialect does not support savepoints — check your JPA provider’s capabilities
Enter fullscreen mode Exit fullscreen mode

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 so 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.

Top comments (1)

Collapse
 
saladlam profile image
Salad Lam
Comment hidden by post author

Some comments have been hidden by the post's author - find out more

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay