目前项目中用到Spring的地方很多,很多功能都能在sping中找到解决方案,正如我现在想要说的缓存实现,Spring Cache已经为我们提供了很好的解决方案,并且提供了默认实现,增加几个注解立刻就能使用,确实挺好,但是在实际使用过程中还是觉得不太方便,主要就是因为要保持缓存注解方法间的名称保持一致,在@CacheEvict中需要指定所有需要清除的缓存信息(通过name,key等属性),方法比较多、比较分散的时候维护难度就会随之提高,稍有不慎就会导致数据的不一致;故此引出今天分享讨论的一种缓存实现:“通过Mybatis的拦截机制实现自动缓存”。
https://github.com/mingjia-vip/MyBatisCache
该实现的逻辑就是参考mybatis的二级缓存,针对使用sql语句查询的dao层,思路就是拦截所有的DAO层方法,解析方法对应的sql语句,对sql语句进行分类处理,分类很简单就两类,query类和update类,query主要是指select语句,update值得就是insert、delete喝update语句了;
对于query类,方法第一次执行的时候查询数据库,将查询结果缓存到cache中,之后在此调用该方法的时候直接从cache中查询;对于update类方法,就是解析sql中的表名,当方法执行成功后,根据表名将cache中所有涉及到该表的所有存储都清除,这样当有query类的方法sql中包含该表的缓存就不存在了,需要重新从数据库查询,然后再缓存,这样也就保证了数据的一致性。
逻辑很简单,下面就说一下实现:
下面是通过xml方式配置sqlSessionFactory的xml片段,注释部分是常规的方式,我们不用,改为指向我们自定义的Factory类:MyBatisCacheSqlSessionFactory,指定工厂方法为:getSqlSessionFactory,这个方法需要两个参数,一个就是常规方法定义的DataSource,再有一个就是cacheService,也就是一个单独的缓存服务,我这里用的redis。
1 |
|
上边是xml的配置方式,注解的方式也可以,原理都是一样的,再有上边配置的mybatisCacheService bean可以根据自己的实际情况更换,喝本文描述的缓存策略实现没有必然关系。
下面看下Factory类的工厂方法:
1 | public static SqlSessionFactory getSqlSessionFactory(DataSource datasource, MybatisCacheService cacheService) { |
工厂方法内容:1,创建自定义的mybatis拦截器mybatisCache;2,创建SqlSessionFactory,并将拦截器配置进去;3,解析mapper下所有的xml文件,将query类型的sql中的表名提取出来并和对应的mapper方法关联起来保存到内存中(MyBatisCacheConfiguration.TABLE_METHOD),此外还有一个MyBatisCacheConfiguration.DIS_CACHE_METHOD,保存的是不需要缓存的方法(通过自定义注解@MyBatisCache(disCache = true)来标示),对缓存策略没有什么影响,就不做说明了。3,最后通过sqlSessionFactoryBean的getObject方法返回实例。
然后再看下自定义的mybatis拦截器的实现,缓存功能逻辑都在这里边了:
1 | public MyBatisInterceptor(MybatisCacheService cacheService) { |
主要逻辑就是对query类和update类的处理,“query”的逻辑主要是‘ else{}’部分,逻辑在最开始已经说了,就是缓存数据,之后cache中没有的才去查数据库;“update”部分处理都是一样的,就是从sql中提取出表名,根据表明从MyBatisCacheConfiguration.TABLE_METHOD中得到方法在缓存中的key,最终清除缓存。
通过这个实现,默认就对所有的mapper方法进行了缓存(如果有不想缓存的加上@MyBatisCache(disCache = true)),不用每个方法都去添加一遍@Cacheable注解,而且不用关心name,key等属性的维护,自动维护数据的一致性;功能上和mybatis的二级缓存逻辑没啥区别,主要就是不用在xml文件中添加cache的标签了,再有就是集成了PageHelper插件,解决了分页插件和mybatis的二级缓存联合使用的数据一致性问题,对于分页数据也可以正常缓存。
该实现很简单,功能也是相对简陋,当然我说的方式也应该可以通过spring cache的自定义方式来实现,再有对于分页插件和Mybatis的二级缓存结合使用问题的解决应该有更简单的解决办法;在此主要是抛砖引玉,希望有想法的朋友分享下好的想法。
写demo的时候pagehelper用的v4,后这对v5的重新设计也做了相应的修改,欢迎指教:
https://github.com/mingjia-vip/MyBatisCache