mybatis——缓存

1 概述

Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的

2 一级缓存

2.1 原理

在这里插入图片描述
如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

2.2 测试一

 @Test
public void testFindUserById(){
    SqlSession sqlSession = null;
    try {
        // 和Spring整合后就省略了
         sqlSession = sqlSessionFactory.openSession();
        // 获得代理对象(到时候就只需要通过Spring容器拿到UserMapper接口的代理对象就可以了)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User u1 = userMapper.findUserById(1);
        System.out.println(u1);
        // 第二次查询
        System.out.println("==============第二次查询====================");
        User u2 = userMapper.findUserById(1);
        System.out.println(u2);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
}
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 249155636.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
==>  Preparing: SELECT * from user WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
==============第二次查询====================
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
Returned connection 249155636 to pool.

第二次查询时,没有去查数据库,直接从sqlSession获取的
在这里插入图片描述
数据缓存在sqlSession的excecutor的localCache中,cache为hashMap

2.3 测试二

   public void testFindUserById(){
        SqlSession sqlSession = null;
        try {
            // 和Spring整合后就省略了
             sqlSession = sqlSessionFactory.openSession();
            // 获得代理对象(到时候就只需要通过Spring容器拿到UserMapper接口的代理对象就可以了)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User u1 = userMapper.findUserById(1);
            System.out.println(u1);
            System.out.println("===============经过一次insert===============");
            userMapper.addUser(new User("123","tianjia","[email protected]","222","2222"));
            sqlSession.commit();
            // 第二次查询
            System.out.println("==============第二次查询====================");
            User u2 = userMapper.findUserById(1);
            System.out.println(u2);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

输出

PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 249155636.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
===============经过一次insert===============
==>  Preparing: INSERT INTO user(username,password,email,phone,address) VALUES (?,?,?,?,?) 
==> Parameters: 123(String), tianjia(String), 111@qq.com(String), 222(String), 2222(String)
<==    Updates: 1
==>  Preparing: select LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 23
<==      Total: 1
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
==============第二次查询====================
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@ed9d034]
Returned connection 249155636 to pool.

在commit之后,sqlsession的缓存被清空了。再次查询时需要查询数据库。

3 二级缓存

1. 第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
2. 第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
3. 如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

3.1 开启二级缓存

sqlConfigMap.xml

 <settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
    <!-- 打印查询语句 -->
    <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

userMapper.xml

<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
    <cache eviction="FIFO" flushInterval="60000" readOnly="true" size="512"/>

3.2 测试一

public void testTwoLevelCache1(){
    SqlSession sqlSession1  = null;
    SqlSession sqlSession2  = null;
    try {
        sqlSession1 = sqlSessionFactory.openSession();
        sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        System.out.println("========第一次查询=================");
        User u1 = userMapper1.findUserById(1);
        System.out.println(u1);
        System.out.println("========第二次查询=================");
        User u2 = userMapper2.findUserById(1);
        System.out.println(u2);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        if (sqlSession1!=null)
            sqlSession1.close();
        if (sqlSession2!=null)
            sqlSession2.close();
    }
}

输出

扫描二维码关注公众号,回复: 4622536 查看本文章
PooledDataSource forcefully closed/removed all connections.
========第一次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Created connection 1970881185.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
========第二次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Created connection 911312317.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@365185bd]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Returned connection 1970881185 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@365185bd]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@365185bd]
Returned connection 911312317 to pool.

在finally里面关闭sqlSession的话,开启了二级缓存,但是始终没有命中。

3.3 测试二

 @Test
    public void testTwoLevelCache2(){
        SqlSession sqlSession1  = null;
        SqlSession sqlSession2  = null;
        try {
            sqlSession1 = sqlSessionFactory.openSession();
            sqlSession2 = sqlSessionFactory.openSession();
            UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
            UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
            System.out.println("========第一次查询=================");
            User u1 = userMapper1.findUserById(1);
            sqlSession1.close();
            System.out.println(u1);
            System.out.println("========第二次查询=================");
            User u2 = userMapper2.findUserById(1);
            sqlSession2.close();
            System.out.println(u2);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

输出

PooledDataSource forcefully closed/removed all connections.
========第一次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Mon Dec 17 19:11:38 CST 2018 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.
Created connection 1970881185.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Returned connection 1970881185 to pool.
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
========第二次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.5
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}

在关闭了sqlSession1和sqlSession2之后
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中

3.4 测试三

@Test
public void testTwoLevelCache3(){
    SqlSession sqlSession1  = null;
    SqlSession sqlSession2  = null;
    SqlSession sqlSession3  = null;
    try {
        sqlSession1 = sqlSessionFactory.openSession();
        sqlSession2 = sqlSessionFactory.openSession();
        sqlSession3 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
        System.out.println("========第一次查询=================");
        User u1 = userMapper1.findUserById(1);
        System.out.println(u1);
        sqlSession1.close();
        userMapper3.addUser(new User("ceshi", "111", "1@com", "123", "sasas"));
        sqlSession3.commit();
        sqlSession3.close();
        System.out.println("========第二次查询=================");
        User u2 = userMapper2.findUserById(1);
        System.out.println(u2);
        sqlSession2.close();
    }catch (Exception e){
        e.printStackTrace();
    }
}
PooledDataSource 
forcefully closed/removed all connections.
========第一次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Mon Dec 17 19:18:58 CST 2018 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.
Created connection 1970881185.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Returned connection 1970881185 to pool.
Opening JDBC Connection
Checked out connection 1970881185 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
==>  Preparing: INSERT INTO user(username,password,email,phone,address) VALUES (?,?,?,?,?) 
==> Parameters: ceshi(String), 111(String), 1@com(String), 123(String), sasas(String)
<==    Updates: 1
==>  Preparing: select LAST_INSERT_ID() 
==> Parameters: 
<==    Columns: LAST_INSERT_ID()
<==        Row: 27
<==      Total: 1
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Returned connection 1970881185 to pool.
========第二次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Checked out connection 1970881185 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
User{id=1, username='修改', password='aaa', email='test@', phone='121212', address='', ordersList=null}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@757942a1]
Returned connection 1970881185 to pool.

在commit之后,二级缓存也失效了

3.5 禁用二级缓存

在select标签中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。

3.6 刷新二级缓存

通过flushCache属性,可以控制select、insert、update、delete标签是否属性二级缓存

  • 默认情况下如果是select语句,那么flushCache是false。
  • 如果是insert、update、delete语句,那么flushCache是true。
<select id="findUserById" parameterType="int" resultType="user" useCache="true" flushCache="true">
		SELECT * FROM user WHERE id = #{id}
</select>

4 整合Ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

4.1 整合思路

  • Mybatis提供了一个Cache接口,该接口有一个默认的实现类PerpetualCache。
  • 只要实现cache接口就可以实现mybatis的二级缓存。
    • mybatis的定位是做持久层框架,对于缓存数据的管理不是mybatis的特长
    • 为了提高mybatis的性能,所以需要mybatis和第三方缓存数据库整合,比如ehcache、memcache、redis等

4.2 整合

1、 引入ehcache的jar包;
2、 在mapper映射文件中,配置cache标签的type为ehcache对cache接口的实现类类型。
3、 加入ehcache的配置文件

4.2.1引入相关依赖

<!-- ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
     <artifactId>ehcache</artifactId>
     <version>2.10.6</version>
 </dependency>
<!-- mybatis-ehcache -->
 <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis-ehcache</artifactId>
     <version>1.0.0</version>
 </dependency>

4.2.2 开启缓存

在需要缓存的mapper配置文件中添加

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

4.2.3 ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<!-- 缓存数据要存放的磁盘地址 -->
	<diskStore path="F:\ehcache" />
	<defaultCache maxElementsInMemory="1000"
                  maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false"
                  timeToIdleSeconds="10" timeToLiveSeconds="10"
                  diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
  • diskStore:指定数据在磁盘中的存储位置。
  • defaultCache:当借助CacheManager.add(“demoCache”)创建Cache时,EhCache便会采用指定的的管理策略
  • 以下属性是必须的:
    • maxElementsInMemory - 在内存中缓存的element的最大数目
    • maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
    • eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
    • overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的:
    • timeToIdleSeconds- 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
    • timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
    • diskSpoolBufferSizeMB -这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
    • diskPersistent- 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
    • diskExpiryThreadIntervalSeconds- 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
    • memoryStoreEvictionPolicy- 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) -->

4.2.5 测试

@Test
    public void testEhcache(){
        SqlSession sqlSession1  = null;
        SqlSession sqlSession2  = null;
        SqlSession sqlSession3  = null;
        try {
            sqlSession1 = sqlSessionFactory.openSession();
            sqlSession2 = sqlSessionFactory.openSession();
            sqlSession3 = sqlSessionFactory.openSession();
            UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
            UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
            UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
            System.out.println("========第一次查询=================");
            User u1 = userMapper1.findUserById(1);
            System.out.println(u1);
            sqlSession1.close();
            Thread.sleep(2000);
            System.out.println("========沉睡2秒第二次查询=================");
            User u2 = userMapper2.findUserById(1);
            System.out.println(u2);
            sqlSession2.close();
            Thread.sleep(10000);
            // 我设置的缓存时间是10秒
            System.out.println("========沉睡12秒第二次查询=================");
            User u3 = userMapper3.findUserById(1);
            System.out.println(u3);
            sqlSession3.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
========第一次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.0
Opening JDBC Connection
Created connection 1666607455.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
pojo.User@79e2c065
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
Returned connection 1666607455 to pool.
========沉睡2秒第二次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.5
pojo.User@79e2c065
========沉睡12秒第二次查询=================
Cache Hit Ratio [mapper.UserMapper]: 0.3333333333333333
Opening JDBC Connection
Checked out connection 1666607455 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
==>  Preparing: SELECT * from user WHERE id=? 
==> Parameters: 1(Integer)
<==    Columns: id, username, password, email, phone, address
<==        Row: 1, 修改, aaa, test@, 121212, 
<==      Total: 1
pojo.User@223d2c72
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6356695f]
Returned connection 1666607455 to pool.

猜你喜欢

转载自blog.csdn.net/ccoran/article/details/85053776