在多线程下,我不断从存储库中获取旧结果。
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateScore(int score, Long userId) {
logger.info(RegularLock.getInstance().getLock().toString());
synchronized (RegularLock.getInstance().getLock()) {
Customer customer = customerDao.findOne(userId);
System.out.println("start:": customer.getScore());
customer.setScore(customer.getScore().subtract(score));
customerDao.saveAndFlush(customer);
}
}
CustomerDao看起来像
@Transactional
public T saveAndFlush(T model, Long id) {
T res = repository.saveAndFlush(model);
EntityManager manager = jpaContext.getEntityManagerByManagedType(model.getClass());
manager.refresh(manager.find(model.getClass(), id));
return res;
}
来自saveAndFlush()
的 JpaRepository
用于立即保存更改并锁定整个代码。但我仍然保持旧的结果。
java.util.concurrent.locks.ReentrantLock@10a9598d[Unlocked]
start:710
java.util.concurrent.locks.ReentrantLock@10a9598d[Unlocked]
start:710
我正在使用springboot和spring data jpa。
我将所有代码都放在测试控制器中,问题仍然存在
@RestController
@RequestMapping(value = "/test", produces = "application/json")
public class TestController {
private static Long testId;
private final CustomerBalanceRepository repository;
@Autowired
public TestController(CustomerBalanceRepository repository) {
this.repository = repository;
}
@PostConstruct
public void init() {
// CustomerBalance customer = new CustomerBalance();
// repository.save(customer);
// testId = customer.getId();
}
@SystemControllerLog(description = "updateScore")
@RequestMapping(method = RequestMethod.GET)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public CustomerBalance updateScore() {
CustomerBalance customerBalance = repository.findOne(70L);
System.out.println("start:" + customerBalance.getInvestFreezen());
customerBalance.setInvestFreezen(customerBalance.getInvestFreezen().subtract(new BigDecimal(5)));
saveAndFlush(customerBalance);
System.out.println("end:" + customerBalance.getInvestFreezen());
return customerBalance;
}
@Transactional
public CustomerBalance saveAndFlush(CustomerBalance customerBalance) {
return repository.saveAndFlush(customerBalance);
}
}
,结果是
start:-110.00
end:-115.00
start:-110.00
end:-115.00
start:-115.00
end:-120.00
start:-120.00
end:-125.00
start:-125.00
end:-130.00
start:-130.00
end:-135.00
start:-130.00
end:-135.00
start:-135.00
end:-140.00
start:-140.00
end:-145.00
start:-145.00
end:-150.00
答案 0 :(得分:3)
我试图重现问题但失败了。我把你的代码,只需很少的更改放入一个Controller并通过请求localhost:8080/test
执行它,并且可以在日志中看到分数按预期减少。 注意:它实际上会产生异常,因为我没有配置视图,但这应该是无关紧要的。
因此,我建议采取以下行动: 从下面拿我的控制器,尽可能少地将它添加到您的代码中。验证它确实有效。然后逐步修改它,直到它与您当前的代码相同。请注意开始产生当前行为的更改。这可能会使事业变得非常明显。如果没有用您找到的内容更新问题。
@Controller
public class CustomerController {
private static String testId;
private final CustomerRepository repository;
private final JpaContext context;
public CustomerController(CustomerRepository repository, JpaContext context) {
this.repository = repository;
this.context = context;
}
@PostConstruct
public void init() {
Customer customer = new Customer();
repository.save(customer);
testId = customer.id;
}
@RequestMapping(path = "/test")
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Customer updateScore() {
Customer customer = repository.findOne(testId);
System.out.println("start:" + customer.getScore());
customer.setScore(customer.getScore() - 23);
saveAndFlush(customer);
System.out.println("end:" + customer.getScore());
return customer;
}
@Transactional
public Customer saveAndFlush(Customer customer) {
return repository.saveAndFlush(customer);
}
}
从OP更新并进行了一些讨论后,我们似乎已将其固定下来:
问题只发生在多个线程上(OP使用JMeter来做这件事10次/秒)。
事务级别序列化似乎也解决了这个问题。
<强>诊断强>
这似乎是一个丢失的更新问题,会导致以下影响:
Thread 1: reads the customer score=10
Thread 2: reads the customer score= 10
Thread 1: updates the customer to score 10-4 =6
Thread 2: updates the customer to score 10-3 =7 // update from Thread 1 is gone.
为什么这不会被同步阻止?
这里的问题很可能是在问题中显示的代码之前发生了读取,因为EntityManager是第一级缓存。
如何修复
这应该被JPA的乐观锁定所捕获,因为这需要一个用@Version
注释的列。
事务级别Serializable可能是更好的选择,如果经常发生这种情况。
答案 1 :(得分:1)
这看起来像是悲观锁定的情况。在您的存储库方法findOneWithLock
中创建lke this:
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import javax.persistence.LockModeType;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select c from Customer c where c.id = :id")
Customer findOneWithLock(@Param("id") long id);
}
并使用它来获取数据库级锁定,这将持续到事务结束:
@Transactional
public void updateScore(int score, Long userId) {
Customer customer = customerDao.findOneWithLock(userId);
customer.setScore(customer.getScore().subtract(score));
}
您的代码中无需使用像RegularLock这样的应用程序级锁。
答案 2 :(得分:0)
问题似乎是在你打电话的时候,
customerDao.saveAndFlush(customer);
提交将不会发生,直到达到方法结束并提交,因为您的代码在
内@Transactional(isolation = Isolation.REPEATABLE_READ)
您可以做的是将交易的propagation
更改为Propagation.REQUIRES_NEW
,如下所示。
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation=Propagation.REQUIRES_NEW)
这将导致在方法结束时创建并提交新事务。在交易结束时,将提交更改。
答案 3 :(得分:0)
使用此行
manager.refresh(manager.find(model.getClass(), id));
您告诉JPA撤消所有更改。来自refresh method
的文档从数据库刷新实例的状态,覆盖对实体所做的更改(如果有)。
删除它,您的代码应按预期运行。