Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 同时运行spring框架事务更新不正确的数据_Java_Spring_Transactional - Fatal编程技术网

Java 同时运行spring框架事务更新不正确的数据

Java 同时运行spring框架事务更新不正确的数据,java,spring,transactional,Java,Spring,Transactional,我创建了一个简单的spring启动应用程序,它将演示银行交易是如何发生的。我创建了一个“帐户”实体,并创建了一个“借记”rest端点 在这里,我同时调用了两次“借记”api,但只借记了一次金额。我想知道如何锁定帐户实体,以便另一个线程读取更新的余额,并再次借记 我尝试将锁定模式类型为悲观写入的“account”实体锁定为悲观写入,但它不起作用 Account.java package hello; import org.hibernate.annotations.CacheConcurrenc

我创建了一个简单的spring启动应用程序,它将演示银行交易是如何发生的。我创建了一个“帐户”实体,并创建了一个“借记”rest端点

在这里,我同时调用了两次“借记”api,但只借记了一次金额。我想知道如何锁定帐户实体,以便另一个线程读取更新的余额,并再次借记

我尝试将锁定模式类型为悲观写入的“account”实体锁定为悲观写入,但它不起作用

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 +
                '}';
    }
}

休息结束点是

    @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");
    }