I need some help with pessimistic entity locking. I'm using PostgreSQL and Spring data JPA (hibernate 5 under the hood) in my application. So, I want to show a task I've faced with.
I have some user accounts with money:
@lombok.Data //Used to generate getters and setters
@Entity
class AccountEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long balance;
}
And payments, that allows to transfer money from one account to another
@lombok.Data
@Entity
class PaymentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long amount;
@OneToOne
@JoinColumn(name="account_from_id")
private AccountEntity accountFrom;
@OneToOne
@JoinColumn(name="account_to_id")
private AccountEntity accountTo;
}
Repositories for both:
interface AccountRepository extends JpaRepository<AccountEntity, Integer> {}
interface PaymentRepository extends JpaRepository<PaymentEntity, Integer> {}
And a service where money transfer performed:
interface PaymentService {
PaymentEntity create(PaymentEntity paymentEntity);
}
@Service
class PaymentServiceImpl implements PaymentService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private PaymentRepository paymentRepository;
@Transactional
@Override
public PaymentEntity create(PaymentEntity payment) {
AccountEntity from = accountRepository.getOne(payment.getAccountFrom().getId()); //want to lock account here
AccountEntity to = accountRepository.getOne(payment.getAccountTo().getId()); //want to lock account here
Long newFromBalance = from.getBalance() - payment.getAmount();
Long newToBalance = to.getBalance() + payment.getAmount();
if (newFromBalance < 0)
throw new RuntimeException("Not enough money for payment");
from.setBalance(newFromBalance);
to.setBalance(newToBalance);
PaymentEntity result = paymentRepository.save(payment); //create payment
accountRepository.save(from); //update account
accountRepository.save(to); //update account
return result; //want to unlock both accounts here
}
}
So, as you can see from code, I want to lock two accounts, involved to the money transfer at the beginning of the transaction (which will guarantee, that they both can't be changed before the transaction commits, and each new payment will get updated balance of the accounts). At the end of the transaction I want to unlock them both.
I've read documentation, and as I and as I understood, for do that I need to update my AccountRepository
:
interface AccountRepository extends JpaRepository<AccountEntity, Integer> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
AccountEntity save(AccountEntity account);
}
The code above must lock from
and to
accounts at the beginning of the transaction and automatically unlock them at the end. But I still have some questions:
- In this case data base locks will be used (not the application one on the java side), because I use multiple instances of the application, that works with the same database. Isn't it?
- Annotation
@Lock(LockModeType.PESSIMISTIC_WRITE)
can be applied only to repository method (not to the service one). Isn't it? @Lock
will lock all entities in a transaction, for which select was performed (from
andto
accounts in my case). Isn't it?- All locked entities will be unlocked automatically when the transaction commits or rollbacks. Isn't it?
- Each payment, which will use account, locked with pessimistic write lock will wait until account will be updated and lock will be released. Isn't it?
I will be very thankful if you provide links to official documentation or just some site, where I can discover this topic by myself.
来源:https://stackoverflow.com/questions/48116330/data-base-pessimistic-locks-with-spring-data-jpa-hibernate-under-the-hood