11.平凡之路-缓存

MyBatis缓存介绍

正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持

  1. 一级缓存基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
  3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 Create/Update/Delete 操作后,默认该作用域下所有 select 中的缓存将被clear。

Mybatis的一级缓存

映射文件

<mapper namespace="com.shxt.dao.UserDao">
   
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    
    <select id="load" parameterType="int" resultMap="BaseResultMapper">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
   
</mapper>

查询 | 一级缓存测试

    @Test
    public void 查询_一级缓存测试(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.getSqlSession();

            User u1 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("第一次查询:"+u1);

            User u2 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("第二次查询:"+u2);

        } finally {
            MyBatisUtils.closeSqlSession(sqlSession);
        }

    }

控制台运行结果说明

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 1, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
第一次查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
第二次查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]

从以上结果中可以看出,两次调用load方法,但是只有一次查询数据库的过程,这种现象产生的原因就是mybatis的一级缓存,并且一级缓存是默认开启的。

查询-变更-查询 | 一级缓存测试

    @Test
    public void 查询_变更_一级缓存测试(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.getSqlSession();

            User u1 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("第一次查询:"+u1);

            User u2 = new User();
            u2.setUser_id(-999);
            u2.setStatus(2);
            //变更数据库
            sqlSession.update(UserDao.class.getName()+".update", u2);

            User u3 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("第二次查询:"+u3);

            //这里一定要提交,不然数据进不去数据库中
            sqlSession.commit();
        }catch (Exception ex) {
            ex.printStackTrace();
        }finally {
            MyBatisUtils.closeSqlSession(sqlSession);
        }

    }

如果中间过程中涉及到CUD操作,那么缓存自动消失,重新查询

控制台运行结果说明

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 1, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
第一次查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - ==>  Preparing: UPDATE sys_user SET status = ? WHERE user_id=? 
DEBUG [main] - ==> Parameters: 2(Integer), -999(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
第二次查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]

Mybatis的二级缓存

默认情况下是没有开启Mybatis二级缓存的,那么我测试一下如下代码,看看运行效果

    @Test
    public void 查询_没有二级缓存测试(){
        //第一个SqlSession
        SqlSession sqlSession1 = null;
        //第二个SqlSession
        SqlSession sqlSession2 = null;
        try {
            sqlSession1 = MyBatisUtils.getSqlSession();

            sqlSession2 = MyBatisUtils.getSqlSession();

            User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("sqlSession1 查询:"+u1);

            User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("sqlSession2 查询:"+u2);

        } finally {
            MyBatisUtils.closeSqlSession(sqlSession1);
            MyBatisUtils.closeSqlSession(sqlSession2);
        }

    }

控制台运行结果说明

DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
sqlSession1 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
Tue Sep 05 16:20:42 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
sqlSession2 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]

两个session,分别查询id为-999 的 User ,那么mybatis与数据库交互了两次,这样说明mybatis现在没有开启二级缓存,需要我们手动的开启。

第一步:序列化持久化类

User.java类去实现Serializable接口,不然MyBatis的二级缓存不好用

public class User implements java.io.Serializable{
    private static final long serialVersionUID = 1L;
}

第二步:手动开启二级缓存

这里的原则是,如果开启了二级缓存,那么在关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。

mybatis-config.xml配置信息

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

cacheEnabled的默认值就是true,所以可以忽略

UserMapper.xml开始二级缓存标签

<mapper namespace="com.shxt.dao.UserDao">
    <!-- 开启二级缓存 -->
    <cache></cache>
   <!-- 省略的部分代码  -->
</mapper>

第三步:测试代码

        @Test
    public void 查询_二级缓存测试() {
        // 第一个SqlSession
        SqlSession sqlSession1 = null;
        // 第二个SqlSession
        SqlSession sqlSession2 = null;

        sqlSession1 = MyBatisUtils.getSqlSession();

        sqlSession2 = MyBatisUtils.getSqlSession();

        User u1 = sqlSession1.selectOne(UserDao.class.getName() + ".load", -999);
        System.out.println("sqlSession1 查询:" + u1);
        MyBatisUtils.closeSqlSession(sqlSession1);// 关闭

        User u2 = sqlSession2.selectOne(UserDao.class.getName() + ".load", -999);
        System.out.println("sqlSession2 查询:" + u2);
        MyBatisUtils.closeSqlSession(sqlSession2);

    }

控制台运行结果说明

DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
sqlSession1 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5
sqlSession2 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]

在默认情况下,当sqlsession执行commit后会刷新缓存,这样的写法等价上面的写法

    @Test
    public void 查询_二级缓存测试(){
        //第一个SqlSession
        SqlSession sqlSession1 = null;
        //第二个SqlSession
        SqlSession sqlSession2 = null;
        try {
            sqlSession1 = MyBatisUtils.getSqlSession();

            sqlSession2 = MyBatisUtils.getSqlSession();

            User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("sqlSession1 查询:"+u1);
            sqlSession1.commit();//强制刷新

            User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load",-999);
            System.out.println("sqlSession2 查询:"+u2);

        } finally {
            MyBatisUtils.closeSqlSession(sqlSession1);
            MyBatisUtils.closeSqlSession(sqlSession2);
        }

    }

当为select语句时:

  • flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
  • useCache默认为true,表示会将本条语句的结果进行二级缓存。

修改映射文件如下

<mapper namespace="com.shxt.dao.UserDao">
    <!-- 开启二级缓存 -->
    <cache></cache>
    
   
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    <!-- 
    flushCache="true" 强制刷新
    
     -->
    <select id="load" parameterType="int" resultMap="BaseResultMapper" flushCache="true">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
</mapper>

再次运行之前commit的提交的测试方法 , 运行结果为:

DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
sqlSession1 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5
DEBUG [main] - ==>  Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=? 
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <==    Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <==        Row: -999, super, super, 刘文铭, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <==      Total: 1
sqlSession2 查询:User [user_id=-999, account=super, password=super, user_name=刘文铭, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]

当为insert、update、delete语句时:

  • flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。
  • useCache属性在该情况下没有

总结

一级缓存

  1. 默认开启
  2. 必须同一个session,如果session对象已经close()过了就不能用了
  3. 查询条件必须一致
  4. 没有执行过session.cleanCache();清理缓存
  5. 没有执行过增删改操作(这些操作都会清理缓存)

二级缓存

1.mybatis-config.xml 中默认配置

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

2.必须手动开启在Mapper.xml中添加

<mapper namespace="com.shxt.dao.UserDao">
    <!-- 开启二级缓存 -->
    <cache></cache>
     <resultMap type="com.shxt.model.User" id="BaseResultMapper">
        <id column="user_id" property="user_id"/>
        <result column="account" property="account"/>
        <result column="password" property="password"/>
        <result column="user_name" property="user_name"/>
        <result column="status" property="status"/>
        <result column="login_time" property="login_time"/>
        <result column="ip" property="ip"/>
        <result column="fk_role_id" property="fk_role_id"/>
    </resultMap>
    
    <sql id="sys_user_columns">
        user_id,account,password,user_name,status,login_time,ip,fk_role_id
    </sql>
    <!-- 
    flushCache="true" 强制刷新
    
     -->
    <select id="load" parameterType="int" resultMap="BaseResultMapper">
        SELECT
            <include refid="sys_user_columns"/>
        FROM
            sys_user
        WHERE user_id=#{user_id}
    </select>
</mapper>

3.映射语句文件中的所有select语句将会被缓存。
4.映射语句文件中的所有insert,update和delete语句会刷新缓存。
5.缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
6.缓存会根据指定的时间间隔来刷新。
7.缓存会存储1024个对象

<cache 
eviction="FIFO"  //回收策略为先进先出
flushInterval="60000" //自动刷新时间60s
size="512" //最多缓存512个引用对象
readOnly="true"/> //只读

说在后面的话,个人感觉MyBatis的缓存意义不大:
A. 面对一定规模的数据量,内置的cache方式就派不上用场了;
B. 对查询结果集做缓存并不是MyBatis框架擅长的,它专心做的应该是sql mapper。采用此框架的Application去构建缓存更合理,比如采用OSCache、Memcached啥的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容