I have created a simple spring boot application which will demonstrate how the bank transactions happens.I have created one 'Account' entity and created one 'debit' rest endpoint.
Here I am calling 'debit' api two times concurrently but only one time amount is debited.I want to know how can I lock the account entity so that another thread will read updated balance and will debit second time too.
I tried to lock 'account' entity with lock mode type as PESSIMISTIC_WRITE but its not working.
Account.java
package hello;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@Table(name = "account")
@Entity // This tells Hibernate to make a table out of this class
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Account {
//@Version
@Column(name="version")
private Integer version;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer userId;
@Column(name = "name")
private String name;
@Column(name="balance")
private int balance;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Account{" +
"userId=" + userId +
", name='" + name + '\'' +
", balance=" + balance +
'}';
}
}
Rest end point is
@GetMapping(path = "/debit")
public ResponseEntity<String> debit() {
Integer withdrawAmount = 100;
Integer userId = 1;
log.debug("debit {} from account id {} ",withdrawAmount,userId);
accountService.debit(userId,withdrawAmount);
return ResponseEntity.badRequest().body("debited");
}
AccountService.java
package hello.service;
import hello.Account;
import hello.AccountRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import java.util.Optional;
@Service
public class AccountService {
final private Logger log = LoggerFactory.getLogger(AccountService.class);
@Autowired
private AccountRepository accountRepository;
@Autowired
private EntityManager entityManager;
@Transactional
public void debit(Integer id,int balance){
Optional<Account> accountOptional = accountRepository.findById(id);
Account account = accountOptional.get();
entityManager.refresh(account, LockModeType.PESSIMISTIC_WRITE);
final int oldBalance = account.getBalance();
log.debug("current balance {}",oldBalance);
account.setBalance(oldBalance-balance);
accountRepository.save(account);
log.debug("debited");
}
}
AccountRepository.java
package hello;
import org.springframework.data.repository.CrudRepository;
// This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository
// CRUD refers Create, Read, Update, Delete
public interface AccountRepository extends CrudRepository<Account, Integer> {
Account findOneByUserId(Integer userId);
}
my database record is - click here to see the image
To test this scenario I have written on bash script debit.sh
curl -I 'http://localhost:8080/demo/debit' &
curl -I 'http://localhost:8080/demo/debit' &
run this with bash debit.sh So it can call same rest endpoint twice.
The output I am getting is
2019-03-27 14:17:36.375 DEBUG 11191 --- [nio-8080-exec-3] hello.MainController : debit 100 from account id 1
2019-03-27 14:17:36.376 DEBUG 11191 --- [nio-8080-exec-4] hello.MainController : debit 100 from account id 1
2019-03-27 14:17:36.394 DEBUG 11191 --- [nio-8080-exec-4] hello.service.AccountService : current balance 100
2019-03-27 14:17:36.394 DEBUG 11191 --- [nio-8080-exec-3] hello.service.AccountService : current balance 100
2019-03-27 14:17:36.395 DEBUG 11191 --- [nio-8080-exec-4] hello.service.AccountService : debited
2019-03-27 14:17:36.396 DEBUG 11191 --- [nio-8080-exec-3] hello.service.AccountService : debited
In both the transactions reading current balance as 100 and debiting same. What I want here is It should update balance as -100.