• 8. Mybatis缓存


    1.一级缓存

    在mybatis中,一级缓存默认是开启的,并且无法关闭。一级缓存存在于SqlSession的生命周期中,在同一个SqlSession中查询时,mybatis会把执行的方法和参数生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map缓存对象中已存在该键值时,则会返回缓存中的对象。

    测试:

    @Test
    public void testQueryUserById() {
        System.out.println(this.userMapper.queryUserById("1"));
        System.out.println(this.userMapper.queryUserById("1"));
    }

    执行日志:

    2018-07-01 17:08:50,156 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    2018-07-01 17:08:50,421 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 242355057.
    2018-07-01 17:08:50,423 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:08:50,476 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:08:50,509 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}
    User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}

    使用:sqlSession.clearCache();可以强制清除缓存

    测试:

    @Test
    public void testQueryUserById() {
        System.out.println(this.userMapper.queryUserById("1"));
        sqlSession.clearCache();
        System.out.println(this.userMapper.queryUserById("1"));
    }

    日志:

    2018-07-01 17:10:51,065 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    2018-07-01 17:10:51,359 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 242355057.
    2018-07-01 17:10:51,360 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:10:51,408 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:10:51,437 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}
    2018-07-01 17:10:51,438 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:10:51,438 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:10:51,440 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}

    执行update、insert、delete的时候,会清空缓存
    测试:

    @Test
    public void testQueryUserById() {
        System.out.println(this.userMapper.queryUserById("1"));
        //sqlSession.clearCache();
    
        User user=new User();
        user.setName("美女");
        user.setId("1");
        userMapper.updateUser(user);
    
        System.out.println(this.userMapper.queryUserById("1"));
    }

    日志:

    2018-07-01 17:18:15,128 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    2018-07-01 17:18:15,399 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 242355057.
    2018-07-01 17:18:15,401 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:18:15,466 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:18:15,492 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}
    2018-07-01 17:18:15,527 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] ==> Preparing: UPDATE tb_user set name = ?, updated = now() WHERE (id = ?); 
    2018-07-01 17:18:15,529 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] ==> Parameters: 美女(String), 1(String)
    2018-07-01 17:18:15,532 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] <== Updates: 1
    2018-07-01 17:18:15,532 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:18:15,533 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:18:15,538 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}

    一级缓存在多线程场景下可能会导致脏读

    测试代码:

    public class MybatisTest2 {
        public static void main(String[] args) throws Exception {
            // 指定全局配置文件
            String resource = "mybatis-config.xml";
            // 读取配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 构建sqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 获取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                User user = userMapper.queryUserById(1);
                System.out.println(user);
                Thread.sleep(1000*60);//此时可以手动修改数据库中数据
                User user2 = userMapper.queryUserById(1);
                System.out.println(user2);
                System.out.println(user==user2);
            } finally {
                sqlSession.close();
            }
        }
    }

    测试结果:两次返回的对象是同一个,第二次返回的是缓存中数据。

    2020-04-21 11:11:18,921 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 1738710757.
    2020-04-21 11:11:18,921 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@67a29ee5]
    2020-04-21 11:11:18,928 [main] [com.mybatis.UserMapper.queryUserById]-[DEBUG] ==>  Preparing: select * from tb_user where id = ? 
    2020-04-21 11:11:19,041 [main] [com.mybatis.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(Integer)
    2020-04-21 11:11:19,158 [main] [com.mybatis.UserMapper.queryUserById]-[DEBUG] <==      Total: 1
    User(id=1, userName=zpc, password=123456, name=鹏程, age=33, sex=1, birthday=Sun Sep 02 00:00:00 CDT 1990, created=2020-04-19 11:14:40.0, updated=2020-04-19 11:14:40.0)
    User(id=1, userName=zpc, password=123456, name=鹏程, age=33, sex=1, birthday=Sun Sep 02 00:00:00 CDT 1990, created=2020-04-19 11:14:40.0, updated=2020-04-19 11:14:40.0)
    true
    2020-04-21 11:12:19,161 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@67a29ee5]

    此时数据库中真实数据:

     解决方法:

      根据数据安全性划分,像评论等对用业务影响不大的数据,保持使用一级缓存,提高查询效率。

      像价格等对业务影响很大的数据,每次查询时清空缓存或者每次查询都新建个sqlSession对象。

    清空缓存:

    方式一:代码里手动调用下面方法

    sqlSession.clearCache();

    方式二:mapper.xml配置文件里对应查询sql增加 flushCache="true" 配置

        <select id="queryUserById" flushCache="true" resultType="com.mybatis.User">
            select * from tb_user where id = #{id}
        </select>

    2.二级缓存

    mybatis 的二级缓存的作用域是一个mapper的namespace ,同一个namespace中查询sql可以从缓存中命中。

    开启二级缓存:

    <mapper namespace="com.zpc.mybatis.dao.UserMapper">
      <cache/>
    </mapper>

    测试:

    @Test
    public void testCache() {
        System.out.println(this.userMapper.queryUserById("1"));
    
        sqlSession.close();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        System.out.println(mapper.queryUserById("1"));
    }

    开启二级缓存,必须序列化:

    public class User implements Serializable{
    private static final long serialVersionUID = -3330851033429007657L;

    日志:

    2018-07-01 17:23:39,335 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    2018-07-01 17:23:39,664 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 2092769598.
    2018-07-01 17:23:39,665 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ? 
    2018-07-01 17:23:39,712 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
    2018-07-01 17:23:39,734 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
    User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}
    2018-07-01 17:23:39,743 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7cbd213e]
    2018-07-01 17:23:39,744 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 2092769598 to pool.
    2018-07-01 17:23:39,746 [main] [com.zpc.mybatis.dao.UserMapper]-[DEBUG] Cache Hit Ratio [com.zpc.mybatis.dao.UserMapper]: 0.5
    User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}

    关闭二级缓存:


    不开启,或者在全局的mybatis-config.xml 中去关闭二级缓存

    <settings>
      <!--开启驼峰匹配-->
      <setting name="mapUnderscoreToCamelCase" value="true"/>
      <!--开启二级缓存,全局总开关,这里关闭,mapper中开启了也没用-->
      <setting name="cacheEnabled" value="false"/>
    </settings>

  • 相关阅读:
    ConcurrentHashMap实现原理及源码分析
    Java原子类实现原理分析
    谈谈Java中的volatile
    谈谈Java中的ThreadLocal
    HashMap实现原理及源码分析
    浅谈Oracle权限体系
    Oracle闪回技术详解
    数据结构(二)之二叉树
    图解排序算法(五)之快速排序——三数取中法
    图解排序算法(四)之归并排序
  • 原文地址:https://www.cnblogs.com/jvStarBlog/p/12736146.html
Copyright © 2020-2023  润新知