zoukankan      html  css  js  c++  java
  • 认识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的二级缓存不适用于哪些对数据实时性要求比较高的场景中。

  • 相关阅读:
    RabbitMQ与AMQP协议详解
    MemCache超详细解读
    ASP.NET Web API通过ActionFilter来实现缓存
    ASP.NET Web API 通过Authentication特性来实现身份认证
    什么是CSR证书申请文件?
    跨平台的 SQL 客户端
    Java生成公私钥对
    git 删除错误提交的commit
    ServiceStack.Text反序列化lowercase_underscore_names格式的JSON
    .NET AES加解密(128位)
  • 原文地址:https://www.cnblogs.com/zhexuejun/p/11080763.html
Copyright © 2011-2022 走看看