相关文章推荐
宽容的苦咖啡  ·  python ...·  3 周前    · 
坏坏的投影仪  ·  “E/AndroidRuntime: ...·  1 年前    · 
豁达的钱包  ·  Tkinter Button按钮控件·  1 年前    · 
暗恋学妹的墨镜  ·  Caused by: ...·  1 年前    · 

在使用JPA的时候。在同一个事务中,对某条数据。 先查询,然后更新,再查询 。由于第2次查询是从缓存取得第1次查询的结果。会出现2种情况:

  • 如果更新是用save()等JPA自带的语句。会更新缓存。第2次查询的和数据库一致。
  • 如果用自己写的更新语句。因为没有更新缓存。第2次查询从缓存获取数据,导致更新的数据第2次取不到。导致查询数据和数据库预期的不一致。
  • 本质上就是要清楚,在同一个事务中,事务没有提交。第2次查询从缓存获取数据。

    二、 证明第二次从缓存获取数据。


    上面的代码中,2个对象的地址都一样是 Organization@9556 。所以第2次查询是从缓存获取的数据。

    三、 参考例子1:

        @Transactional
        @Override
        public void update() {
            Organization organization = organizationRepo.findById(1803L).get();
            organization.setName("Test step1ppp33");
            //更新(因为是在事务中,所以更新了缓存,但是没有保存到数据库)
            organizationRepo.save(organization);
            ////查询数据
            Organization org2 = organizationRepo.findById(1803L).get();
            //获得的数据是 org2.getName()===Test step1ppp33 。证明没有从数据库拿,是从同一个事务缓存中取得的。
            System.out.println("org2.getName()==="+org2.getName());
    

    上面代码中org2.getName()===Test step1ppp33 。证明没有从数据库拿,是从同一个事务缓存中取得的。

    四、 参考例子2:

       @Transactional
        @Override
        public void update() {
            Organization organization = organizationRepo.findById(1803L).get();
             * @Modifying
             * @Query("UPDATE Organization o set o.name='myUpdate' WHERE o.id=:id")
             *void myUpdate(@Param("id") Long id);
    		 // 只做了一个更新语句,没有更新缓存的organization变量。
            organizationRepo.myUpdate(1803L);
            //查询缓存数据
            Organization org2 = organizationRepo.findById(1803L).get();
            //org2.getName()===Test step1ppp33 ,证明从缓存获取的数据。更新的数据没有被查询到,
            System.out.println("org2.getName()==="+org2.getName());
    

    自己写了一个update语句,并没有更新缓存的数据,所以第2次查询出来的name值并没有改变。

    五、先更新,后查询。会查询出更新后的数据。

    //把名字更新为 “myUpdate周4”
    organizationRepo.myUpdate(1803L);
    Organization organization = organizationRepo.findById(1803L).get();
    //“myUpdate周4”
    System.out.println(organization.getName());
    

    可以看到最后打印出myUpdate周4表示查询出了更新后的数据。hibernate生成语句的顺序如下:

  • update Organization。
  • select from Organization。
  • select from Organization。
  • 六、 高并发陷进。

    如果我们要写一个方法methodA(),目的是减掉某个商品数量amount。就会有2种情况:

  • 使用save()语句更新。如下:
  • public void methodA() {
    	Goods goods = goodsRepo.findById(1);
    	goods.setAmount(goods.getAmount() - quantity);
    	goodsRepo.save(goods);
    

    这个时候缓存也被更新。同一个事务中,外部methodB()调用methodA()之后,在methodB()中使用goodsRepo.findById(1)查询库存。能保证查询的缓存数量和数据库一致。
    但是高并发下,如果期间有另一个线程把商品的Amount更新为0,当前线程执行完之后商品数和当前线程一样,就导致其他线程的更新丢失。
    2. 使用自定义语句更新amount。如下:

  • 检查了数量的自定义update语句。
  • 	@Modifying
    	@Query("UPDATE Goods g SET g.amount=g.amount - :amount WHERE g.id=:id AND g.amount >=:amount")
    	void myUpdate(@Param("id") Long id,@Param("amount") Long amount);
     
  • 减数量的逻辑。
  • public void methodA() {
    	Goods goods = goodsRepo.findById(1);
    	//减数量
    	goodsRepo.myUpdate(1,amount);
    

    这个时候。同一个事务中,外部methodB()调用methodA()之后,在methodB()中使用goodsRepo.findById(1)查询库存。就会出现和预期不一致的情况。

    总结: 必须让预期的数据库数量,和缓存的数量一致。