Java 同时运行spring框架事务更新不正确的数据
我创建了一个简单的spring启动应用程序,它将演示银行交易是如何发生的。我创建了一个“帐户”实体,并创建了一个“借记”rest端点 在这里,我同时调用了两次“借记”api,但只借记了一次金额。我想知道如何锁定帐户实体,以便另一个线程读取更新的余额,并再次借记 我尝试将锁定模式类型为悲观写入的“account”实体锁定为悲观写入,但它不起作用 Account.javaJava 同时运行spring框架事务更新不正确的数据,java,spring,transactional,Java,Spring,Transactional,我创建了一个简单的spring启动应用程序,它将演示银行交易是如何发生的。我创建了一个“帐户”实体,并创建了一个“借记”rest端点 在这里,我同时调用了两次“借记”api,但只借记了一次金额。我想知道如何锁定帐户实体,以便另一个线程读取更新的余额,并再次借记 我尝试将锁定模式类型为悲观写入的“account”实体锁定为悲观写入,但它不起作用 Account.java package hello; import org.hibernate.annotations.CacheConcurrenc
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 +
'}';
}
}
休息结束点是
@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");
}
使用bash debit.sh运行此命令
所以它可以调用同一个rest端点两次
我得到的结果是
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
在这两种交易中,当前余额均为100,借记金额相同。
我想要的是,它应该将余额更新为-100。请阅读以下答案:
在您的情况下,我觉得将事务设置为
readcommitted
就可以了,但如果不是这样,Serializable
应该可以完全解决您的问题,但这会带来性能成本。请阅读以下答案:
在您的情况下,我觉得将事务设置为
readcommitted
就可以了,但是如果不是这样,Serializable
应该可以完全解决您的问题,但这会带来性能成本。我通过将服务中的借记方法设置为“已同步”来解决。但这会降低性能,因为一个请求将等待另一个请求完成。我认为正确的解决方案是使用锁定机制-
@Transactional
已同步的公共作废借方(整数id,整数余额){
可选accountOptional=accountRepository.findById(id);
Account=accountOptional.get();
//entityManager.refresh(account,LockModeType.悲观写入);
final int oldBalance=account.getBalance();
debug(“当前余额{}”,oldBalance);
账户余额(旧余额);
accountRepository.save(account);
log.debug(“借记”);
}
我通过将服务中的debit方法设置为“synchronized”解决了这个问题。但这会降低性能,因为一个请求将等待另一个请求完成。我认为正确的解决方案是使用锁定机制-
@Transactional
已同步的公共作废借方(整数id,整数余额){
可选accountOptional=accountRepository.findById(id);
Account=accountOptional.get();
//entityManager.refresh(account,LockModeType.悲观写入);
final int oldBalance=account.getBalance();
debug(“当前余额{}”,oldBalance);
账户余额(旧余额);
accountRepository.save(account);
log.debug(“借记”);
}
我尝试在方法上添加@Transactional(隔离=隔离.SERIALIZABLE),但仍然存在相同的问题。您能否尝试将CacheConcurrencyStrategy
更新为Transactional
,并告知结果?您使用的值与您需要的完全相反。@org.hibernate.annotations.Cache(usage=cacheconcurrencysttrategy.TRANSACTIONAL)没有成功。我尝试添加@TRANSACTIONAL(isolation=isolation.SERIALIZABLE)关于方法,但仍然存在相同的问题。您能否尝试将您的CacheConcurrencyStrategy
更新为TRANSACTIONAL
,并告知结果?您使用的值与您需要的正好相反。使用@org.hibernate.annotations.Cache(用法=cacheconcurrencysttrategy.TRANSACTIONAL)失败
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);
}
curl -I 'http://localhost:8080/demo/debit' &
curl -I 'http://localhost:8080/demo/debit' &
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
@Transactional
synchronized 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");
}