Mybatis内置了强大的事务查询缓存机制。正确使用Mybatis缓存机制可以有效提升应用程序性能。因为我们应用程序的大部分性能消耗一般都与数据库查询有关,如果能够有效命中缓存,适当避免或减少与数据库的交互,一定是提升应用程序性能的最佳选择。但是,缓存机制是一把双刃剑。缓存使用不当可能会导致数据不一致。一定不能为了解决性能问题而引入数据不一致。这是每个开发人员都应该具备的基本认识。所以,才能优雅的解决性能问题,安全的避免数据不一致的问题。我们有必要对Mybatis的缓存机制有一个透彻的了解。理解Mybatis缓存机制Mybatis提供了两种不同的缓存机制,也就是我们常说的一级缓存和二级缓存。一级缓存:也叫LocalCache,是基于SqlSession的缓存,也就是说缓存是存储在SqlSession这一级的,会随着SqlSession的关闭而消失。二级缓存:在整个SqlSession生命周期中,二级缓存的范围定义在命名空间级别,对应mapper.xml文件的定义。跨范围效应需要特殊定义。其实程序员应该认真对待Mybatis的二级缓存机制,因为一级缓存机制是默认开启的,几乎不存在数据不一致的问题。二级缓存机制需要通过配置来开启,很容易用不好。导致数据不一致的问题。而且一级缓存使用起来应该是很简单的,所以Mybatis官网上几乎没有对一级缓存的介绍,官网上关于缓存的描述几乎都是关于二级缓存的缓存(参考https://mybatis.org/mybatis-3...)。建议大家仔细研究学习官网文档。官网已经说明的内容我们不再赘述。本文主要补充一些官网文章中没有提到的关于Mybatis缓存机制的技术细节。一级缓存一级缓存是默认开启的,我们无需任何配置就可以使用Mybatis的一级缓存。如果不想开启Mybatis的一级缓存,可以通过Settings配置全局禁用(localCacheScope=STATEMENT)。如果不想只为某条SQL语句启用一级缓存,可以在映射xml文件中为该SQL语句配置flushCache为true。注意flushCache对一级缓存和二级缓存都有效。建议采用Mybatis的默认配置,开启一级缓存,尤其是那些涉及初级程序员的项目,他们可能考虑较少的性能问题,同一个数据可能会在一个事务中查询多次,这时候,我们当然可以通过框架设计、训练、codereview等方式尽量避免这种情况,但是如果无法避免的话,至少Mybatis的一级缓存可以在一定程度上帮助我们避免不必要的数据库请求.二级缓存#打开官网,很清楚需要在映射配置文件中手动开启二级缓存,否则Mybatis不会自动开启二级缓存。官网摘录:MyBatis内置了强大的事务查询缓存机制,可以非常方便的进行配置和定制。我们对MyBatis3中的缓存实现做了很多改进,使其更加强大和易于配置。默认情况下,只启用本地会话缓存,即只缓存一个会话中的数据。要启用全局二级缓存,只需在你的SQL映射文件中添加一行:基本上就是这样。这条简单语句的效果如下:映射语句文件中所有select语句的结果都会被缓存起来。映射语句文件中的所有插入、更新和删除语句都会刷新缓存。缓存会使用最近最少使用算法(LRU,LeastRecentlyUsed)算法来清除不需要的缓存。缓存不定期刷新(即没有刷新间隔)。缓存持有1024个对列表或对象的引用(以查询方法返回的为准)。缓存被视为读/写缓存,这意味着获取的对象不共享,并且可以由调用者安全地修改,而不会干扰其他调用者或线程所做的潜在修改。当不开启二级缓存时,Mybatis底层使用BaseExecutor对象执行SQL,再使用CachingExecutor执行SQL。CachingExecutor在创建时会持有Mapper初始化过程中装饰器模式一层层创建的SynchronizedCache对象。sql语句的执行结果最终被这个wrapper持有,实现缓存。二级缓存#作用范围我们已经说过,二级缓存的作用范围是命名空间。Mybatis官网也有一句提示:提示缓存只作用于缓存标签所在映射文件中的语句。如果混合使用JavaAPI和XML映射文件,默认情况下不会缓存公共接口中的语句。您需要使用@CacheNamespaceRef注释指定缓存范围。这句话一定要引起足够的重视,因为如果这个地方使用不当,很容易造成缓存一致性问题。我们在文章开头也提到了数据一致性的问题。这里简单解释一下:引入缓存的目的是为了尽可能减少数据库I/O,以提高系统性能。如果第一个查询从数据库中获取id=1并缓存用户数据。当第二次执行查询获取到id=1的用户数据时,Mybatis会检查该数据是否已经存在于缓存中。如果是这样,Mybatis就不会再执行数据库查询了。缓存中的数据会直接返回给请求者。这时候,我们也常称之为缓存命中。如果两次查询之间有修改id=1的用户信息的操作怎么办?如果Mybatis在不知道这个修改的情况下直接返回缓存的数据,会造成数据不一致,因为应用程序返回给前端的数据是不正确的。当然,Mybatis有自己的解决方案。这个方案是在更新数据的时候刷新缓存。刷新缓存实际上就是清除缓存。那么修改后的第一次查询会因为无法命中缓存而通过数据库查询获取数据。这样的话就不会造成数据不一致的问题了。但现实往往比你想象的要复杂。比如你的用户查询的sql语句定义在mapperA.xml中,而用户更新的sql语句定义在mapperB.xml中。在这种情况下,悲剧发生了。用户更新后,根本不会触发用户信息查询的缓存刷新,因为两者作用域不同,根本不在同一个世界。从技术角度来说,Mybatis底层在缓存时是根据namespace或者mapper.xml文件来创建缓存对象的。上述用户查询语句和用户更新语句的缓存对象不同,所以执行完用户更新语句。之后用户查询语句所在的缓存对象就完全不会刷新了!在开发过程中一定要注意这个问题,否则你可能只知道项目中出现一些奇怪的问题是Mybatis二级缓存机制导致的,而不知道具体的底层原因。出现问题后,二级缓存要么关闭,要么就是随意配置,flushCache或者userCache,有时候可能偶然解决了问题,但是底层的技术原理还是没有掌握,对个人帮助不大.二级缓存#刷新间隔由参数flushInterval配置,例如:设置Mybatis二级缓存刷新间隔为60秒,意思是如果两次查询间隔超过60秒,就会刷新缓存。建议这个参数要么不配置,Mybatis默认不刷新缓存,要么配置大一些,比如3小时。PreviousMybatisL拦截器NextMybatisL事务管理机制