MyBatis的一级缓存和二级缓存

本文详细介绍了MyBatis的一级缓存和二级缓存机制,包括缓存的工作原理、开启方式、缓存的清空时机及如何配置使用。通过实际代码示例,帮助开发者更好地理解和掌握MyBatis缓存的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mybatis缓存介绍

如下图,是mybatis一级缓存和二级缓存的区别图解:

这里写图片描述

一级缓存:

Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。

二级缓存:

Mybatis二级缓存是多个SqlSession共享的,其作用域可以是当前Mapper,也可以是跨越多个Mapper,可以通过<cache/><cache-ref namespace=""/>进行设置。

注意:MyBatis默认实现的一二级缓存是使用HashMap存储的。

在开启二级缓存的情况下:

在同一个Mapper二级缓存里面,执行多次sql语句的情况是:首先查找二级缓存里面是否有数据,如果没有就找一级缓存,再没有,才会查找数据库。

在二级缓存没有开启的情况下

查找数据的顺序是:查看一级缓存里面是否能够找到,找不到就在数据库里面找!!

Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。

一级缓存

原理

下图是根据id查询用户的一级缓存图解:

这里写图片描述

  • 一级缓存区域是根据SqlSession为单位划分的。
  • 每次查询会先从缓存区域(这里指的是一级缓存,这里还没有开启二级缓存)找,如果找不到从数据库查询,查询到数据将数据写入缓存。
  • Mybatis内部存储缓存(包括一级和二级缓存)使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
  • 一级缓存里面sqlSession执行insert、update、delete等操作会清空一级缓存区域。注意一级缓存清空的时机是insert、update、delete中,commit事务之前。

下面的代码用到的基础类:

实体类Item.java(商品实体):

public class Item{
    private Integer id;
    private String name;
    private Double price;
    private String detail;
    private byte[] pic;
    private Date createtime;

实体类User.java(用户实体):

public class User{
    private Integer id;
    private String username;
    private Date birthday;
    private Integer sex;
    private String address;

ItemMapper.xml映射文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.domarvel.dao.ItemMapper">
    <select id="findItemByLikeName" resultType="cn.domarvel.po.Item">
        SELECT * FROM items WHERE name LIKE concat('%',#{name},'%')     
    </select>
</mapper>

UserMapper.xml映射文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.domarvel.dao.UserMapper">
    <select id="findUserByLikeName" resultType="cn.domarvel.po.User">
        SELECT * FROM user WHERE username LIKE concat('%',#{username},'%')
    </select>
    <update id="updateUserById">
        UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    </update>
</mapper>

ItemMapper.java

public interface ItemMapper {
    public List<Item> findItemByLikeName(String name) throws Exception;
}

UserMapper.java

public interface UserMapper {
    public List<User> findUserByLikeName(String username) throws Exception;
    public void updateUserById(User user) throws Exception;
}

测试代码一(关闭sqlSession清空缓存区):

    @Test
    public void testUserAndItems() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");


        //关闭sqlSession就清空一级缓存
        sqlSession.close();


        sqlSession=sqlSessionFactory.openSession();
        userMapper=sqlSession.getMapper(UserMapper.class);
        itemMapper=sqlSession.getMapper(ItemMapper.class);


        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

日志输出:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 988458918.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - Returned connection 988458918 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 988458918 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1

测试代码二(更新操作清空缓存):

    @Test
    public void testUserAndItemsClearOneCache() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        User targetUpdateUser=new User(34, "小Q", new Date(), 1, "四川南充");
        userMapper.updateUserById(targetUpdateUser);
        //只有提交事务后,数据库数据才会被修改。但是可以看到这里并没有commit事务操作就清空一级缓存了,说明清空一级缓存的时机是在userMapper.updateUserById()方法中

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

日志输出

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 988458918.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 小Q(String), 2017-04-12 21:45:45.107(Timestamp), 1(Integer), 四川南充(String), 34(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1

总结:

  • 一级缓存总是开启的。
  • 清空缓存有两种情况:1.关闭sqlSession,缓存被清空。2.用同一个sqlSession进行增加,更新,删除操作,sqlSession里面的缓存被清空。注意这里一级缓存被清空的时机是insert、update、delete中,commit事务之前。
  • 手动通过代码sqlSession.clearCache();也能够清空一级缓存。
  • 清空缓存是清空sqlSession里面存储的所有信息,包括不同sql查询的信息
一级缓存应用

正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。

service{
    //开始执行时,开启事务,创建SqlSession对象
    //第一次调用mapper的方法findUserById(1)

    //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    //方法结束,sqlSession关闭
}

如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。

二级缓存

原理

这里写图片描述

二级缓存默认是关闭的,所以首先开启mybatis的二级缓存。

  • sqlSession1去查询用户id为1的用户信息,查询到的用户信息将在sqlSession1被关闭时存储到二级缓存,未关闭sqlSession时是被存储到一级缓存中的。

  • sqlSession2去查询用户id为1的用户信息,去二级缓存中找是否存在数据,如果存在直接从二级缓存中取出数据。二级不存在就在一级缓存中查找。一级不存在就在数据库中查找。

  • 如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。注意清空是删除所有缓存数据。

二级缓存与一级缓存区别,二级缓存的范围更大,一个Mapper里面多个sqlSession共享一个二级缓存区域。

每个Mapper配置文件都可以有一个二级缓存,通过标签<cache/>进行配置,通过<cache/>配置的是Mapper中独立的二级缓存。如果某个Mapper不想要重新申请独立的二级缓存,而是想要共用其它Mapper的二级缓存,可以通过<cache-ref namespace=""/>进行分享共用。

开启二级缓存

mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中声明使用哪种类型的二级缓存或者是共用其它Mapper的二级缓存。<cache/>默认是使用MyBatis自身实现的二级缓存,以后我们可能会用到分布式缓存,那个时候我们就需要配置其它的缓存实现。

<caChe/>标签的常用参数讲解

第一步:

在核心配置文件SqlMapConfig.xml的settings标签中加入

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

第一步目的:开启总的二级缓存

名称描述允许值默认值
cacheEnabled对在此配置文件下的所有cache 进行全局性开/关设置。true或者falsetrue


sqlMapConfig.xml核心配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 和spring整合后 environments配置将废除-->
    <!-- 
        下面的两个标签environments和environment对应的属性default和id能够填入的值:
        work:工作模式
        development:开发模式
        注意:不能够乱写
    -->
    <properties resource="db.properties"></properties>

    <settings>
        <!-- 打开延迟加载 的开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载即按需要加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <environments default="development">
        <environment id="development">
        <!-- 使用jdbc事务管理,事务控制由MyBatis-->
            <transactionManager type="JDBC" />
        <!-- 数据库连接池,由MyBatis管理-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

    <!-- 加载映射文件 -->
    <mappers>
        <!-- 扫描加载Mapper文件 -->
        <package name="cn.domarvel.dao"/>
    </mappers>
</configuration>

db.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true
jdbc.username=root
jdbc.password=

第二步:
在Mapper映射文件中修改配置:

ItemMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.domarvel.dao.ItemMapper">
    <!-- 开启当前Mapper的独立二级缓存,当然可以通过配置共用其它Mapper的二级缓存。 -->
    <cache/>
    <select id="findItemByLikeName" resultType="cn.domarvel.po.Item">
        SELECT * FROM items WHERE name LIKE concat('%',#{name},'%')     
    </select>
</mapper>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.domarvel.dao.UserMapper">
    <!-- 共用ItemMapper中的二级缓存 -->
    <cache-ref namespace="cn.domarvel.dao.ItemMapper"/>
    <select id="findUserByLikeName" resultType="cn.domarvel.po.User">
        SELECT * FROM user WHERE username LIKE concat('%',#{username},'%')
    </select>
    <update id="updateUserById">
        UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    </update>
</mapper>

第三步:

在开启了二级缓存后,pojo等实体类还必须实现序列化接口!!

Item.java实体类:

public class Item implements Serializable{
    private Integer id;
    private String name;
    private Double price;
    private String detail;
    private byte[] pic;
    private Date createtime;

User.java实体类:

public class User implements Serializable{
    private Integer id;
    private String username;
    private Date birthday;
    private Integer sex;
    private String address;

实现序列化接口的目的是为了将缓存数据取出执行反序列化操作。因为二级缓存数据存储介质多种多样,不一定在内存。存储在内存中就不需要序列化操作。

理论测试:

不同Mapper共用一个二级缓存

测试代码:

@Test
    public void testUserAndItems() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        //关闭流,清空一级缓存。在清空一级缓存的同时才会保存数据到二级缓存。注意是关闭流的sqlSession的时候才会保存数据到二级缓存。
        //想想为什么要关闭sqlSession才会保存数据到二级缓存??因为未关闭sqlSession之前一直是使用一级缓存,而关闭后,一级缓存就才会用不了,这个时候就应该把数据从一级缓存转到二级缓存。
        sqlSession.close();

        sqlSession=sqlSessionFactory.openSession();
        userMapper=sqlSession.getMapper(UserMapper.class);
        itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

输出日志:

DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1250391581.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Returned connection 1250391581 to pool.
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.3333333333333333
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.5

可以看出,两个Mapper共用一个二级缓存成功!!
同时我们也能够知道当sqlSession关闭时,会把一级缓存清空,并且会把数据保存到二级缓存。
其中Cache Hit Ratio是缓存的击中率!

测试数据查询顺序:

    @Test
    public void testUserAndItems() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        //如果不关闭sqlSession流,数据就还没有被保存到二级缓存中。
        //而我们查找数据时顺序是:二级缓存->一级缓存->数据库
        //既然二级缓存没有,他就会到一级缓存中查找,一级缓存只要没有被更新、删除、增加数据和关闭sqlSession就不会被清空。
        //一级缓存的清空时机是更新、删除、增加中,commit提交事务之前

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

输出日志:

DEBUG [main] - Reader entry: <?xml version="1.0" encoding="UTF-8" ?>
DEBUG [main] - Checking to see if class cn.domarvel.dao.ItemMapper matches criteria [is assignable to Object]
DEBUG [main] - Checking to see if class cn.domarvel.dao.UserMapper matches criteria [is assignable to Object]
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1250391581.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0

可以看到几次的二级缓存查找的点中率都为0.0,而且也没有发送sql语句去查询数据库,所以只能是从一级缓存中查找到数据。
所以上面的操作并没有发生把数据保存到二级缓存中的事件。

这也证实了开启二级缓存后的查找数据顺序:二级缓存->一级缓存->数据库
其中没有开启二级缓存时查找数据的顺序:一级缓存->数据库

测试当数据未存储到二级缓存时,反而中途清空一级缓存,第二次查找时会怎么找数据:

当然,不用测试都能知道,肯定是数据库查找啦!!哈哈。

    @Test
    public void testUserAndItems() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        //通过修改数据来清空一级缓存,其中清空一级缓存的时机是在事务commit之前,updateUserById()中。读者可以用DEBUG调试查看!!
        User targetUpdateUser=new User(34, "小狼", new Date(), 1, "四川南充");
        userMapper.updateUserById(targetUpdateUser);
        sqlSession.commit();

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

日志输出:

DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1250391581.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 小狼(String), 2017-04-12 19:05:57.134(Timestamp), 1(Integer), 四川南充(String), 34(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1

注意:就算清空了一级缓存,当sqlSesion.close()关闭后,也能够保存数据到二级缓存!!

二级缓存清空时机:

    @Test
    public void testUserAndItemsTwoCacheXX() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");


        //关闭sqlSession就清空一级缓存
        sqlSession.close();


        sqlSession=sqlSessionFactory.openSession();
        userMapper=sqlSession.getMapper(UserMapper.class);
        itemMapper=sqlSession.getMapper(ItemMapper.class);


        User targetUpdateUser=new User(34, "小K", new Date(), 1, "四川南充");
        userMapper.updateUserById(targetUpdateUser);
        //注意,这里并没有进行sqlSession.commit()进行事务提交,在事务提交之前就已经"清空"缓存数据了。"清空"二级缓存是在insert,update,delete中。

        List<User> users3=userMapper.findUserByLikeName("小明");
        List<Item> items3=itemMapper.findItemByLikeName("本");

    }

日志输出:

DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1250391581.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Returned connection 1250391581 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1250391581 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 小K(String), 2017-04-14 08:59:45.899(Timestamp), 1(Integer), 四川南充(String), 34(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.3333333333333333
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.5
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1

总结:

看日志可以看到,虽然有击中率,但是还是查询了数据库,通过update方法没有真正的清空二级缓存数据,因为击中率的问题。但是起到的效果和完全清空二级缓存数据的效果一样,都查询了数据库,并且返回的也是最新数据库中的数据。

想要真正的清空二级缓存,可以使用sqlSession.commit()操作。
读者可以用上面的例子进行试验,在update后把sqlSession.commit()操作加上。

对于笔者我来说,清空二级缓存的时机还是在update中。

笔者认为本质上清空缓存的时机是在:insert,update,delete中,sqlSession.commit()事务之前。而sqlSession.commit()是清除表面上的东西。

清空二级缓存的时机和清空一级缓存类似,都有在insert、update、delete中,sqlSession.commit()之前。其中清空一级缓存的时机还有,在sqlSession关闭时也会清空一级缓存。

这里要说一点<caChe/>里面有个readOnly="true"的参数,如果为true则返回缓存对象的引用,如果为false则返回缓存对象的copy对象。这个过程比较慢,但是安全。readOnly的默认值为false。

温馨提示:

有很多程序员误以为只要是在同一个namespace下就是共有一个二级缓存。其实并不是这样的。事实是每个Mapper通过<cache/>能够申请独立且互不干扰的二级缓存,如果某个Mapper想不申请二级缓存,而是想要共用其它的二级缓存,可以通过<cache-ref namespace=""/>进行配置共用。在相同命名空间下也是这样操作的。

MyBatis可以很细粒度的控制某些sql语句是否需要二级缓存:

在statement中设置useCache=false可以禁用当前select语句的二级缓存,即该sql查询不会去访问二级缓存,当sqlSession关闭时,数据也不会保存到二级缓存。默认情况是true,即该sql使用二级缓存。

    <select id="findUserByLikeName" resultType="cn.domarvel.po.User" useCache="true">
        SELECT * FROM user WHERE username LIKE concat('%',#{username},'%')
    </select>

总结:如果数据想要比较新的可以设置此条sql不进行二级缓存。注意 userCache="false" 只能够关闭当前sql语句的二级缓存,并不能关闭一级缓存,所以如果一级缓存中有比较老的数据可以使用sqlSession.clearCache();进行清空一级缓存。

修改UserMapper.xml文件,修改查询不需要二级缓存:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.domarvel.dao.UserMapper">
    <!-- 共用ItemMapper中的二级缓存 -->
    <cache-ref namespace="cn.domarvel.dao.ItemMapper"/>
    <select id="findUserByLikeName" resultType="cn.domarvel.po.User" useCache="true">
        SELECT * FROM user WHERE username LIKE concat('%',#{username},'%')
    </select>
    <update id="updateUserById">
        UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    </update>
</mapper>

关闭User模糊查询的二级缓存后的效果

    @Test
    public void testUserAndItemsTwoCacheShutDown() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        sqlSession.close();
        sqlSession=sqlSessionFactory.openSession();
        userMapper=sqlSession.getMapper(UserMapper.class);
        itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

输出结果:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1177377518.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee]
DEBUG [main] - Returned connection 1177377518 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1177377518 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.5

可以看到关闭User模糊查询的二级缓存后,User模糊查询并没有通过二级缓存来保存数据,也没有通过二级缓存取数据,和Item的查询比较后就知道区别。
因为中途关闭了sqlSession,清空了一级缓存,所以User就从数据库中查找,而Item就从二级缓存中查找。

如果需求中需要最新的数据,我们可以这么做:

    @Test
    public void testUserAndItemsTwoCacheClearCache() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        //在这里我们已经指定关闭了User模糊查询的二级缓存
        //清空一级缓存
        sqlSession.clearCache();

        List<User> users2=userMapper.findUserByLikeName("小明");
    }

日志输出:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1177377518.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@462d5aee]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3

我们看到,每一次的查询都是发送的sql语句,所以这查询的是最新的数据,适合需要最新数据的需求!!

刷新缓存(清空缓存)
  • 在同一个Mapper的二级缓存中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。flushCache="true"能够防止脏读。
  • 设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

设置insert、update、delete是否刷新缓存的设置代码如下:

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">

总结:一般执行完insert、update、delete后都会刷新缓存(虽然要commit后才会生效,但是它在commit之前就已经清空缓存了,可以通过DEBUG调试测试),flushCache=true表示刷新缓存,这样可以避免数据库脏读。

在测试一级缓存前要关闭二级缓存,虽然能够达到目的,但是看起来不好看!!

测试flushCache=“true”是否对一级缓存有效果:

    @Test
    public void testUserAndItemsClearxOneCache() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        User targetUpdateUser=new User(34, "小Q", new Date(), 1, "四川南充");
        userMapper.updateUserById(targetUpdateUser);
        //这里并没有写sqlSession.commit()进行数据库更新,但是在updateUserById()时就会清空缓存
        //但是我们设置了刷新缓存为false,我们的目的就是测试此设置是否对一级缓存有效果。

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

日志输出:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 988458918.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3aeaafa6]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 小Q(String), 2017-04-13 17:31:05.49(Timestamp), 1(Integer), 四川南充(String), 34(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1

可以看到,fluchCache="false"对关闭一级缓存的刷新没有效果。

现在测试更新操作时是否关闭二级缓存的刷新缓存效果:

    @Test
    public void testUserAndItemsClearxTwoCache() throws Exception{
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        ItemMapper itemMapper=sqlSession.getMapper(ItemMapper.class);

        List<User> users1=userMapper.findUserByLikeName("小明");
        List<Item> items1=itemMapper.findItemByLikeName("本");

        sqlSession.close();

        sqlSession=sqlSessionFactory.openSession();
        userMapper=sqlSession.getMapper(UserMapper.class);
        itemMapper=sqlSession.getMapper(ItemMapper.class);

        User targetUpdateUser=new User(34, "小Q", new Date(), 1, "四川南充");
        userMapper.updateUserById(targetUpdateUser);
        //这里没有写sqlSession.commit()进行数据库更新,但是在updateUserById()时就会清空缓存

        List<User> users2=userMapper.findUserByLikeName("小明");
        List<Item> items2=itemMapper.findItemByLikeName("本");
    }

日志输出:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1250391581.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 小明(String)
DEBUG [main] - <==      Total: 3
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.0
DEBUG [main] - ==>  Preparing: SELECT * FROM items WHERE name LIKE concat('%',?,'%') 
DEBUG [main] - ==> Parameters: 本(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - Returned connection 1250391581 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1250391581 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4a87761d]
DEBUG [main] - ==>  Preparing: UPDATE user SET username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 小Q(String), 2017-04-13 17:39:49.396(Timestamp), 1(Integer), 四川南充(String), 34(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.3333333333333333
DEBUG [main] - Cache Hit Ratio [cn.domarvel.dao.ItemMapper]: 0.5

很明显,更新操作执行后,第二次进行查询数据时并没有发送sql语句查询数据库,说明flushCache="false" 不进行刷新二级缓存配置生效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值