认识Mybatis的一二级缓存

认识Mybatis的一二级缓存


       一次完整的数据库请求,首先根据配置文件生成SqlSessionFactory,再通过SqlSessionFactory开启一次SqlSession,在每一个SqlSession中维护着一个Executor实例,通过Executor实例,可以获取到Statement然后结合输入的参数,

查询结果集,Mybatis的一级缓存是在发生executor阶段,在executor内部维护着一个PerpetualCache实例完成缓存,PerpetualCache由一个id和HashMap组成,一级缓存和Sqlsession实例进行绑定的,每一次sqlSession都有一个

PerpetualCache进行缓存,不同的sqlSession之间不会相互影响,所以当SqlSession关闭或者commit的时候,一级缓存就会消失。二级缓存是mapper级别的缓存,二级缓存和namespace进行绑定,多个SqlSession去操作同一个Mapper的sql语句,

多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的,二级缓存也是作用于Executor实例之中,由CachingExecutor对Executor的实现类BaseExecutor类进行增强,从而增加了缓存的功能,二级缓存需要将查询结果序列化,

所以用来接收结果的pojo需要实现Serializable接口。

一、一级缓存

     可以通过代码来认识一级缓存,需要准备以下jar包:

     (1)Mybatis的核心包

    (2) log4j日志包

     搭建一个简单的demo:

     

     自行创建一个user表,字段自定,mybatisConfig.xml,Mapper.xml, Mapper.java, ServiceExecutor.java内容如下:

     mybatisConfig.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>
    <settings>
       <setting name="logImpl" value="LOG4J"/> 
      <!--   <setting name="cacheEnabled" value="true" /> --> 
    </settings>

    <environments default="development">
       <environment id="development">
          <transactionManager type="JDBC"></transactionManager>
          <dataSource type="POOLED">
             <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
             <property name="url" value="jdbc:mysql://localhost:3306/goods"/>
             <property name="username" value="root"/>
             <property name="password" value="www1928..com"/>
          </dataSource>
       </environment>
    </environments>
    <mappers>
       <mapper resource="com/idt/mybatis/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.com.idt.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

Mapper.xml

package com.idt.mybatis.mapper;

import java.util.List;
import java.util.Map;


public interface UserMapper {
   public List<Map<String, String>> getUserList();
}

ServiceExecutor.java

package com.idt.mybatis.service;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.idt.mybatis.mapper.UserMapper;

public class ServiceExecutor {
   
    private static SqlSessionFactory sqlSessionFactory;
    static {
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(ServiceExecutor.class.getClassLoader().getResourceAsStream("mybatisConfig.xml"));
    }
    /*
     * 一级缓存验证
     */
    public void queryUserList() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        /*第一次查询*/
        List<Map<String, String>> userList1 = userMapper.getUserList();
        //清除缓存
        //sqlSession.clearCache(); 
        /*第二次查询*/
        List<Map<String, String>> userList2 = userMapper.getUserList();
        sqlSession.close();
    }
    
    public static void main(String[] args) {
        ServiceExecutor executor = new ServiceExecutor();
        executor.queryUserList();
    }
}

执行查询,打印控制台的日志如下:

可以看到代码中的两次查询却只执行了一次,所以在sqlSession没有关闭的情况下,第二次查询到的是缓存里的数据,通过sqlSession.clearCache()方法我们清空缓存然后再执行:

清空缓存之后,可以发现两次查询都取访问了数据库,前面已经说过,一级缓存只作用在sqlSession级别,所以当sqlSession关闭或者提交之后都会清空缓存。

源码中如下

在SSM环境中,Mybatis的SqlSessionFactory交由Spring维护,在每次Sqlsession执行查询之后都是自动close,所以在SSM环境,一级缓存是失效的。

二、二级缓存

由于一级缓存适用环境少,所以为了Mybatis为了提供能适用更多场景的二级缓存,二级缓存不再局限于SqlSession级别,而是作用于Mapper(namespace)级别,或者说在SqlSessionFactory级别

在原来的demo做以下添加:

在mybatisConfig.xml添加 

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

在Mapper.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="com.idt.mybatis.mapper.UserMapper">
    <cache>
    </cache>
    <select id="getUserList" resultType="map">
        select * from t_user
    </select>
    <select id="getUserByname" resultType="map">
        select * from t_user where loginname = #{loginname}
    </select>
</mapper>

在Mapper.java新增接口

public Map<String, String> getUserByname(@Param("loginname") String name);

在ServiceExecutor.java中添加方法:

   /*
     * 二级缓存验证
     */
    public void queryUserByname() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        Map<String, String> user1 = userMapper1.getUserByname("liSi");
        System.out.println("第一次查询:" + user1.toString());
        sqlSession1.close();    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        Map<String, String> user2 = userMapper2.getUserByname("liSi");
        System.out.println("第二次查询:" + user2.toString());
        sqlSession1.close();
    }

执行该方法:

可以看出第二次的数据来自于缓存,两次虽然是不同的sqlsession但是有着相同的sqlSessionFactory,查询sql以及相同的查询参数,mybatis会根据这个进行判断是否两次查询一样,来减少数据的连接然后提高查询效率,

但是问题也随之而来,如果在两次查询之间添加一个修改操作,那么mybatis还会用缓存之中的数据吗?前面提高过,二级缓存是作用于namespace级别,也就是在一个namespace中进行update,del, insert等操作都是清空缓存,

在之前的源码截图中可以看到,所以我们要验证不再一个namespace下,会不会因为出现缓存出现脏读,将demo做如下修改:

增加新的mapper映射UserMapper2,

UserMapper2.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="com.idt.mybatis.mapper.UserMapper">
    <delete id="updateUser" >
       update t_user set loginname=#{newname} where loginname = #{loginname}
    </delete>
</mapper>

UserMapper.java

package com.idt.mybatis.mapper;

import org.apache.ibatis.annotations.Param;

public interface UserMapper2 {
    public int updateUser(@Param("newname") String newname, @Param("loginname") String name);
}

mybatisConfig.xml

<mapper resource="com/idt/mybatis/mapper/UserMapper2.xml"/>

ServiceExecutor.java

    public void queryUserByname() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        Map<String, String> user1 = userMapper1.getUserByname("liSi");
        System.out.println("第一次查询:" + user1.toString());
        sqlSession1.close();
        
        /*更新操作*/
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        UserMapper2 userMapper3 = sqlSession3.getMapper(UserMapper2.class);
        userMapper3.updateUser("zhangSan", "liSi");
        
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        Map<String, String> user2 = userMapper2.getUserByname("liSi");
        System.out.println("第二次查询:" + user2.toString());
        sqlSession1.close();
    }

从上面的打印日志中可以看出,在进行了更新操作之后,第二次的查询仍然读取了缓存中的数据,造成了脏读,所以mybatis的二级缓存不适用于哪些对数据实时性要求比较高的场景中。

原文地址:https://www.cnblogs.com/zhexuejun/p/11080763.html