【踩坑】mybatis一级缓存执行相同代理对象

23 阅读2分钟
@Test  
@DefaultTransactional  
public void mybatisCacheTest(){  
    //第一次查询  
    BtBillOutboundModel searchModel = new BtBillOutboundModel();  
    searchModel.setBillNo("OB2300000122");  
  
    BtBillOutboundModel searchModel2 = new BtBillOutboundModel();  
    searchModel2.setBillNo("OB2300000122");  
  
    BtBillOutboundModel outboundModel = btBillDao.selectOne(searchModel);  
    BtBillOutboundModel outboundModel1 = btBillDao.selectOne(searchModel2);  
  
    System.out.println(outboundModel == outboundModel1); //true
  
}

问题描述:

在两个工作日后发送邮件这里,遇到了这样的问题,我想先查询退仓单当前的信息,用btBillDao.selectOne方法查询得到退仓单信息对象A(此时状态值为”待预约“),然后在确认提货方法中,因为复用了提货方法的接口,在方法中同样也进行了一遍btBillDao.selectOne方法查询退仓单信息得到对象B,这两次查询的参数都是一样的。接着再提货方法中更新了对象B的退仓单状态后,回到发送邮件方法中使用第一个退仓单对象进行状态值的判断,如果等于”待预约“则发送邮件。而问题则是对象A的状态不等于”待预约“了

问题解决:

经过排查,我想到错误应该是对象引用的问题,A和B应该指向了同一个对象导致B的值更新A也更新了。所以在查询到A对象后新建了变量保存A中状态值,经过测试后问题解决

问题思考:

  • 在调用提货方法时的入参并不是对象A,因此不是对象传递导致的引用相同的问题。经过祥哥的指点,这个问题是因为myabtis中的一级缓存导致的。
  • 一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。也就是当在发送邮件方法中得到了A对象,在提货方法中用相同的查询语句和条件得到的B对象会走一级缓存,直接使用A对象。也就是这两个不同名称在不同方法中创建的对象的引用是同一个对象,也就会导致问题的发生
  • 同时,mybatis中的一级缓存是有事务级别的,只有同一个commit操作中的相同查询才会走一级缓存,在进行commit操作后会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。所以如果两个查询操作不是在一个事务中也不会发生这个问题。

问题总结:

该问题的发生是因为两个方法添加了事务,它们处于同一个事务中,所以执行的相同sql查询会先查询一级缓存,导致两个方法操作的实际上是同一个对象。