MyBatis缓存分为一级缓存和二级缓存

一级缓存

MyBatis的一级缓存指的是在一个Session域内,session为关闭的时候执行的查询会根据SQL为key被缓存(跟mysql缓存一样,修改任何参数的值都会导致缓存失效)

1)单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询,是可以使用以及缓存的,示例代码如下

public class Test {
public static void main(String[] args) throws IOException {
String config = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(config);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
System.out.println(session.selectOne("selectUserByID", 1));
// 同一个session的相同sql查询,将会使用一级缓存
System.out.println(session.selectOne("selectUserByID", 1));
// 参数改变,需要重新查询
System.out.println(session.selectOne("selectUserByID", 2));
// 清空缓存后需要重新查询
session.clearCache();
System.out.println(session.selectOne("selectUserByID", 1));
// session close以后,仍然使用同一个db connection
session.close();
session = factory.openSession();
System.out.println(session.selectOne("selectUserByID", 1));
}
}

输出如下

DEBUG - Openning JDBC Connection
DEBUG - Created connection 10044878.
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
1|test1|19|beijing
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 2(Integer)
2|test2|18|guangzhou
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - Returned connection 10044878 to pool.
DEBUG - Openning JDBC Connection
DEBUG - Checked out connection 10044878 from pool.
DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@9945ce]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
1|test1|19|beijing

看以看出来,当参数不变的时候只进行了一次查询,参数变更以后,则需要重新进行查询,而清空缓存以后,参数相同的查询过的SQL也需要重新查询,而且使用的数据库连接是同一个数据库连接,这里要得益于我们在mybatis-config.xml里面的datasource设置

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"> </transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>

注意datasource使用的是POOLED,也就是使用了连接池,所以数据库连接可回收利用,当然这个environment属性在集成spring的时候是不需要的,因为我们需要另外配置datasource的bean.

2) 跟Spring集成的时候(使用mybatis-spring)

直接在dao里查询两次同样参数的sql

@Repository
public class UserDao extends SqlSessionDaoSupport {
public User selectUserById(int id) {
SqlSession session = getSqlSession();
session.selectOne("dao.userdao.selectUserByID", id);
// 由于session的实现是SqlSessionTemplate的动态代理实现
// 它已经在代理类内执行了session.close(),所以无需手动关闭session
return session.selectOne("dao.userdao.selectUserByID", id);
}
}

观察日志

DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8] was not registered for synchronization because synchronization is not active
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e389b8]
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74] was not registered for synchronization because synchronization is not active
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169da74]
DEBUG - Returning JDBC Connection to DataSource

这里执行了2次sql查询,看似我们使用了同一个sqlSession,但是实际上因为我们的dao继承了SqlSessionDaoSupport,而SqlSessionDaoSupport内部sqlSession的实现是使用用动态代理实现的,这个动态代理sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(),执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session()(关于这一点见下面mybatis的官方文档),当然也无法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的.

官方文档摘要

MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.

You cannot call SqlSession.commit()SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.

二级缓存

二级缓存就是global caching,它超出session范围之外,可以被所有sqlSession共享,它的实现机制和mysql的缓存一样,开启它只需要在mybatis的配置文件开启settings里的

<setting name="cacheEnabled" value="true"/>

以及在相应的Mapper映射文件(例如userMapper.xml)里开启

<cache />
添加这一行后,会产生如下效果:
  • 映射语句文件中的所有select 语句将会被缓存。
  • 映射语句文件中的所有insert,update 和delete 语句会刷新缓存。
  • 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表(比如no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024 个引用。
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

以上所有的属性都可以通过<cache /> 元素来修改:

<mapper namespace="dao.userdao">
... select statement ...
<!-- Cache 配置 -->
<cache
eviction="FIFO"// 回收策略
flushInterval="60000"// 刷新间隔
size="512" // 引用数目
readOnly="true" // 只读
/>
</mapper>

以上表明首先创建了一个先进先出策略的缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。

可用的收回策略有:

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是LRU。

属性flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

属性size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。

属性readOnly(只读)属性可以被设置为true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是false。

需要注意的是global caching的作用域是针对Mapper的Namespace而言的,也就是说只在有在这个Namespace内的查询才能共享这个cache.例如上面的 dao.userdao namespace, 下面是官方文档的介绍。

It's important to remember that a cache configuration and the cache instance are bound to the namespace of the SQL Map file. Thus, all statements in the same namespace as the cache are bound by it.

例如下面的示例,我们执行两次对同一个sql语句的查询,观察输出日志

@RequestMapping("/getUser")
public String getUser(Model model) {
User user = userDao.selectUserById(1);
model.addAttribute(user);
return "index";
}

当我们访问两次 /getUser 这个url,查看日志输出

DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812] was not registered for synchronization because synchronization is not active
DEBUG - Cache Hit Ratio [dao.userdao]: 0.0
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - JDBC Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver] will not be managed by Spring
DEBUG - ooo Using Connection [jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8, UserName=root@localhost, MySQL-AB JDBC Driver]
DEBUG - ==> Preparing: SELECT * FROM user WHERE id = ? 
DEBUG - ==> Parameters: 1(Integer)
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659812]
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Invoking afterPropertiesSet() on bean with name 'index'
DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher'
DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index'
DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index'
DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index'
DEBUG - Successfully completed request
DEBUG - Returning cached instance of singleton bean 'sqlSessionFactory'
DEBUG - DispatcherServlet with name 'dispatcher' processing GET request for [/user/getUser]
DEBUG - Looking up handler method for path /user/getUser
DEBUG - Returning handler method [public java.lang.String controller.UserController.getUser(org.springframework.ui.Model)]
DEBUG - Returning cached instance of singleton bean 'userController'
DEBUG - Last-Modified value for [/user/getUser] is: -1
DEBUG - Creating a new SqlSession
DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92] was not registered for synchronization because synchronization is not active
DEBUG - Cache Hit Ratio [dao.userdao]: 0.5
DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@539a92]
DEBUG - Rendering view [org.springframework.web.servlet.view.JstlView: name 'index'; URL [/index.jsp]] in DispatcherServlet with name 'dispatcher'
DEBUG - Added model object 'org.springframework.validation.BindingResult.user' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'index'
DEBUG - Added model object 'user' of type [bean.User] to request in view with name 'index'
DEBUG - Forwarding to resource [/index.jsp] in InternalResourceView 'index'
DEBUG - Successfully completed request

可以看出第二次访问同一个url的时候相同的查询 hit cache了,这就是global cache的作用。

<select />标签中关于缓存的使用

属性

flushCache:将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
useCache:将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。如果设置为false,那么即使启用了<cache />该条select的结构也不会被二级缓存。

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

Mybatis缓存使用注意事项

如果数据缓存在本地,另一个系统修改数据库或者手动修改数据库时,会出现脏数据问题。

  • 一级缓存
    Myatis的一级缓存默认为SESSION,底层用PerpetualCache,里面使用map做为存储,并没有做太多条件限制。

  • 二级缓存
    MyBatis虽然全局配置开启缓存,但是还是取决于是否使用了<cache/>标签,如果使用了二级缓存,需要注意:
    每个<cache />代表一个单独的二级缓存,如果多个Mapper需要共享同一个二级缓存,就需要使用<cache-ref/>。如果一个Mapper中查询数据时,使用了多表联查,当另一个Mapper更新相关数据时,如果没有共享一个Cache,那么下一次该Mapper查询时,就会出现读到脏数据。

  • 使用二级缓存一般基于以下原则:
    不经常变动的数据,但经常会使用
    数据量比较大,系统多处会用到。或者跨系统用。
    对性能有特别要求的地方。
    滥用二级缓存,有可能反而会降低性能,特别是根据条件查询缓存

二级缓存源码浅析

mybatis下关于cache的所有类
  • 包内容浅析
    cache包下,分为装饰者包(其中有些关于cache的具体类文件,比如阻塞加锁cache的委派、fifo的cache委派,使用装饰者模式起到了在不同使用场景灵活委派功能的目的,具有良好的扩展性)和cache底层实现包,以上类都实现了Cache接口。
  • 调用顺序:
    mybatis的关于cache有个CacheBuilder类,该类使用建造者模式,在其中有个public Cache build()方法负责具体的<cache />属性的组装。通过MapperBuilderAssistant类调用CacheBuilder进行具体的装配顺序操作。

CacheBuilder类

CacheBuilder类

MapperBuilderAssistant类

MapperAnnotationBuilder类(负责处理mybatis的注解)

XMLMapperBuilder类(负责处理mybatismapper的xml)

Mybatis(三):MyBatis缓存详解的更多相关文章

  1. Mybatis(三) 映射文件详解

    前面说了全局配置文件中内容的详解,大家应该清楚了,现在来说说这映射文件,这章就对输入映射.输出映射.动态sql这几个知识点进行说明,其中高级映射(一对一,一对多,多对多映射)在下一章进行说明. 一.输 ...

  2. Mybatis SQL映射文件详解

    Mybatis SQL映射文件详解 mybatis除了有全局配置文件,还有映射文件,在映射文件中可以编写以下的顶级元素标签: cache – 该命名空间的缓存配置. cache-ref – 引用其它命 ...

  3. Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解

    封面:洛小汐 作者:潘潘 做大事和做小事的难度是一样的.两者都会消耗你的时间和精力,所以如果决心做事,就要做大事,要确保你的梦想值得追求,未来的收获可以配得上你的努力. 前言 上一篇文章 <My ...

  4. MyBatis的动态SQL详解

    MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑,本文详解mybatis的动态sql,需要的朋友可以参考下 MyBatis 的一个强大的特性之一通常是它 ...

  5. 转载 Spring、Spring MVC、MyBatis整合文件配置详解

    Spring.Spring MVC.MyBatis整合文件配置详解   使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. ...

  6. MyBatis中@MapKey使用详解

    MyBatis中@MapKey使用详解我们在上一篇文章中讲到在Select返回类型中是返回Map时,是对方法中是否存在注解@MapKey,这个注解我也是第一次看到,当时我也以为是纯粹的返回单个数据对象 ...

  7. 深入浅出Mybatis系列四-配置详解之typeAliases别名(mybatis源码篇)

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(三)---配置详解之properties ...

  8. Spring MVC、MyBatis整合文件配置详解

    Spring:http://spring.io/docs MyBatis:http://mybatis.github.io/mybatis-3/ Building a RESTful Web Serv ...

  9. 利用Intellij+MAVEN搭建Spring+Mybatis+MySql+SpringMVC项目详解

    http://blog.csdn.net/noaman_wgs/article/details/53893948 利用Intellij+MAVEN搭建Spring+Mybatis+MySql+Spri ...

  10. idea spring+springmvc+mybatis环境配置整合详解

    idea spring+springmvc+mybatis环境配置整合详解 1.配置整合前所需准备的环境: 1.1:jdk1.8 1.2:idea2017.1.5 1.3:Maven 3.5.2 2. ...

随机推荐

  1. 主席树 STL+二分【p3939】数颜色

    Description 小 C 的兔子不是雪白的,而是五彩缤纷的.每只兔子都有一种颜色,不同的兔子可能有 相同的颜色.小 C 把她标号从 \(1\) 到 \(n\) 的 \(n\) 只兔子排成长长的一 ...

  2. Manacher【p1210】回文检测

    题目描述--->P1210 回文检测 分析: 看到回文显然想到了manacher算法(线性求解回文串问题 如果不了解还是去敲一下板子,学习一下比较好.-->manacher 题目要求我们求 ...

  3. HDU1142 A Walk Through the Forest(dijkstra)

    A Walk Through the Forest Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Jav ...

  4. TDD开发案例

    前段时间把一个界面框架完成了,今天基于这个框架开发一个小模块,在这里把这个模块设计的全过程记录下来,希望大家讨论并指正. 一.起因 公司交给我一个任务,为测试员写一个手机模拟界面,以方便她们的手机短信 ...

  5. [Atcoder Grand Contest 002] Tutorial

    Link: AGC002 传送门 A: …… #include <bits/stdc++.h> using namespace std; int a,b; int main() { sca ...

  6. HDU 6017 Girls Love 233(多态继承DP)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=6017 [题目大意] 给出一个只包含2和3的串,你可以花费两个智力值交换相邻的两个字符 问在智力值不 ...

  7. 软件配置篇-java下载及安装

    1.进入java官网下载合适版本: 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.htm ...

  8. Exercise03_03

    import java.util.Scanner; public class LinearEquation { public static void main(String[] args){ doub ...

  9. WebLogic Server 多租户资源迁移

    重新建立一个动态集群,并启动,注意监听地址不能和其他集群重合 选择相应的资源组进行迁移, 迁移后,访问新的地址成功. 通过OTD负载均衡器访问原有的地址成功. 直接访问原来后台地址失败,表示资源确实已 ...

  10. solr6.6 索引 word文档

    本文是solr6.6 导入 pdf/doc/txt/json/csv/xml文件 的继续,上篇在索引文件,唯独07格式的word文档不能正常抽取数据,进过研究测试终于,记录下属过程. 其它步骤基本和s ...