11.平凡之路-缓存

作者: 胖先森 | 来源:发表于2017-09-05 17:11 被阅读0次

    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啥的。

    相关文章

      网友评论

        本文标题:11.平凡之路-缓存

        本文链接:https://www.haomeiwen.com/subject/mytvdxtx.html