首页 >> 大全

mybatis缓存,从一个“灵异”事件说起

2023-12-20 大全 24 作者:考证青年

刚准备下班走人,被一开发同事叫住,让帮看一个比较奇怪的问题:同一个接口的查询方法,第一次返回与第二次返回结果不一样,百思不得其解!

问题

Talk is cheap. Show me the code. 该问题涉及的主要代码实现包括

接口定义

public interface GoodsTrackMapper extends BaseMapper {List listGoodsTrack(@Param("criteria") GoodsTrackQueryCriteria criteria);
}

xml定义

定义

@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class GoodsTrackService extends BaseService {@Autowiredprivate GoodsTrackMapper goodsTrackMapper;public List listGoodsTrack(GoodsTrackQueryCriteria criteria){return goodsTrackMapper.listGoodsTrack(criteria);}public List goodsTrackList(GoodsTrackQueryCriteria criteria){List listGoodsTrack = goodsTrackMapper.listGoodsTrack(criteria);Map goodsTrackDTOMap = new HashMap();for (GoodsTrackDTO goodsTrackDTO : listGoodsTrack){String goodsId = String.valueOf(goodsTrackDTO.getGoodsId());if (!goodsTrackDTOMap.containsKey(goodsId)){goodsTrackDTOMap.put(goodsId, goodsTrackDTO);}else {GoodsTrackDTO goodsTrack = goodsTrackDTOMap.get(goodsId);int num = goodsTrack.getGoodsNum()   goodsTrackDTO.getGoodsNum();goodsTrack.setGoodsNum(num);}}List  list = new ArrayList(goodsTrackDTOMap.values());return list;}
}@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class GoodsOrderService extends BaseService {@Autowiredprivate GoodsTrackService goodsTrackService;@Overridepublic GoodsOrderDTO create(GoodsOrderDTO goodsOrderDTO) {//...List rs1 = goodsTrackList(criteria);//...List rs2 = listGoodsTrack(criteria);//...}
}

大致逻辑就是在 定义了两个查询方法,一个是直接从数据库中获取数据,第二个是从数据库中获取数据后进行了一些加工(通过某个字段进行合并累加,类似sum group by),然后在 的同一个方法(该方法是一个事务方法 )中调用这两个查询,发现rs2中的数据存在问题, 期望是都应该与数据库表的数据一致,但其中部分数据却与查出后进行了修改的rs1中的一致。

定位

初步看, 方法直接调用的方法 .() 没做任何应用层的处理,第一反应是缓存的原因。 我问前面的查询有没有改变查询返回的结果(一开始没细看具体实现),答曰没有。折腾一阵后,返过去细看 的实现,果然还是眼见为实、耳听为虚。在该方法中,通过对返回的列表进行分组,对进行累加,最后返回累加后的几个对象。但是在累加的时候,是直接作用于返回结果对象的,明明就是改变了查询结果(居然说没有?!!)。 这就是问题所在了,在同一个事务中,对同一个查询(同样的sql,同样的参数)的返回结果进行了缓存(称为一级缓存),下一次做同样的查询时,如果中间没有任何更新操作,则直接返回缓存的数据,而在本例中因为对缓存数据做了人为的修改,所以最后导致查出的数据与数据库不一致。

_mybatis缓存,从一个“灵异”事件说起_mybatis缓存,从一个“灵异”事件说起

缓存机制

简单介绍下的两级缓存机制

如何开启二级缓存

需要在-.xml中设置:


然后在的xml文件的 下设置cache相关配置:

 

支持的属性:

也可以使用 来与另一个共享二级缓存

解决

已经定位到是由于的一级缓存导致,那如何解决本文提到的问题呢? 基本上有三个解决方向。

使用缓存的方案

mybatis缓存,从一个“灵异”事件说起_mybatis缓存,从一个“灵异”事件说起_

既然要使用缓存,那就不能更改缓存的数据,此时我们可以在需要更改数据的地方把数据做一次副本拷贝,使其不改变缓存数据本身, 如

for (GoodsTrackDTO goodsTrackDTO : listGoodsTrack){String goodsId = String.valueOf(goodsTrackDTO.getGoodsId());if (!goodsTrackDTOMap.containsKey(goodsId)){goodsTrackDTOMap.put(goodsId, ObjectUtil.clone(goodsTrackDTO));}else {GoodsTrackDTO goodsTrack = goodsTrackDTOMap.get(goodsId);int num = goodsTrack.getGoodsNum()   goodsTrackDTO.getGoodsNum();goodsTrack.setGoodsNum(num);}
}

使用.clone()方法(工具包中提供)对需要更改的数据做副本拷贝。

禁用缓存的方案

在xml的sql定义中添加 ="true" 的配置,使该查询不使用缓存,如下

禁用缓存的另一种方案是将一级缓存直接设置为来进行全局禁用,在-.xml中配置:


避开缓存的方案

再定义一个实现相同查询的方法,id不一样来避开使用相同的缓存,这种做法就不怎么优雅了。

避开缓存的另一种做法是不使用事务,使两个查询不在一个中,但有时候事务是必须的,所以得分场景来。

另外由于的缓存都是基于本地的,在分布式环境下可能导致读取的数据与数据库不一致,比如一个服务实例两次读取中间,另一个服务实例对数据进行了更新,则后一次读取由于缓存还是读取的旧数据,而不是更新后的数据,可能导致问题。这时可以通过将缓存设置为级别来禁用缓存,通过Redis,等来提供分布式的全局缓存。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了