精华内容
下载资源
问答
  • 一级缓存session级别 二级缓存sessionFactory级别 一级缓存: 基于PerpetualCache的HashMap本地缓存,其... 二级缓存一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 M...

    一级缓存session级别
    二级缓存sessionFactory级别

    1. 一级缓存: 基于PerpetualCache的HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
    2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。

      3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。   

    Mybatis一级缓存测试

    package me.gacl.test;
    
    import me.gacl.domain.User;
    import me.gacl.util.MyBatisUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    /**
     * @author gacl
     * 测试一级缓存
     */
    public class TestOneLevelCache {
        
        /*
         * 一级缓存: 也就Session级的缓存(默认开启)
         */
        @Test
        public void testCache1() {
            SqlSession session = MyBatisUtil.getSqlSession();
            String statement = "me.gacl.mapping.userMapper.getUser";
            User user = session.selectOne(statement, 1);
            System.out.println(user);
            
            /*
             * 一级缓存默认就会被使用
             */
            user = session.selectOne(statement, 1);
            System.out.println(user);
            session.close();
            /*
             1. 必须是同一个Session,如果session对象已经close()过了就不可能用了 
             */
            session = MyBatisUtil.getSqlSession();
            user = session.selectOne(statement, 1);
            System.out.println(user);
            
            /*
             2. 查询条件是一样的
             */
            user = session.selectOne(statement, 2);
            System.out.println(user);
            
            /*
             3. 没有执行过session.clearCache()清理缓存
             */
            //session.clearCache(); 
            user = session.selectOne(statement, 2);
            System.out.println(user);
            
            /*
             4. 没有执行过增删改的操作(这些操作都会清理缓存)
             */
            session.update("me.gacl.mapping.userMapper.updateUser",
                    new User(2, "user", 23));
            user = session.selectOne(statement, 2);
            System.out.println(user);
            
        }
    }
    

    Mybatis二级缓存测试

    1、开启二级缓存,在userMapper.xml文件中添加如下配置

    <mapper namespace="me.gacl.mapping.userMapper">
    <!-- 开启二级缓存 -->
    <cache/>
    
    package me.gacl.test;
    
    import me.gacl.domain.User;
    import me.gacl.util.MyBatisUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.junit.Test;
    
    /**
     * @author gacl
     * 测试二级缓存
     */
    public class TestTwoLevelCache {
        
        /*
         * 测试二级缓存
         * 使用两个不同的SqlSession对象去执行相同查询条件的查询,第二次查询时不会再发送SQL语句,而是直接从缓存中取出数据
         */
        @Test
        public void testCache2() {
            String statement = "me.gacl.mapping.userMapper.getUser";
            SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
            //开启两个不同的SqlSession
            SqlSession session1 = factory.openSession();
            SqlSession session2 = factory.openSession();
            //使用二级缓存时,User类必须实现一个Serializable接口===> User implements Serializable
            User user = session1.selectOne(statement, 1);
            session1.commit();//不懂为啥,这个地方一定要提交事务之后二级缓存才会起作用
            System.out.println("user="+user);
            
            //由于使用的是两个不同的SqlSession对象,所以即使查询条件相同,一级缓存也不会开启使用
            user = session2.selectOne(statement, 1);
            //session2.commit();
            System.out.println("user2="+user);
        }
    }
    

    二级缓存补充说明

      1. 映射语句文件中的所有select语句将会被缓存。

      2. 映射语句文件中的所有insert,update和delete语句会刷新缓存。

      3. 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。

      4. 缓存会根据指定的时间间隔来刷新。

      5. 缓存会存储1024个对象   

    <cache 
    eviction="FIFO"  <!--回收策略为先进先出-->
    flushInterval="60000" <!--自动刷新时间60s-->
    size="512" <!--最多缓存512个引用对象-->
    readOnly="true"/> <!--只读-->

    最后给你们推荐一个群,如果你还想提升自己

    欢迎加入Java技术交流群:659270626

    本群提供免费的学习指导 提供Spring源码、MyBatis、Netty、Redis,Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx、分布式、高并发、性能调优、等架构技术架构资料 以及免费的解答

    不懂的问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 

     

     

    展开全文
  • 在这分别就基于Spring Boot下mybatis的简单实现、多数据源实现和动态数据源加载三种模式做整理。 网上有很多把多数据源和动态数据源混为一谈,在这里说明一下,该项目中多数据源指的是:个项目中需要用到不止...
  • <?xml version="1.0" encoding="UTF-8" ?> SELECT * FROM user where id = #{id}
  • 我们知道Mybatis一级缓存二级缓存,底层都是用HashMap实现的 key为CacheKey对象(后续说原因),value为从数据库中查出来的值。 Mybatis二级缓存模块是装饰器的典型实现,不清楚装饰者模式的看如下文章 面试官...

    在这里插入图片描述

    介绍

    要想回答这个问题,必须把一级缓存和二级缓存的实现搞明白,详细介绍一下

    我们知道Mybatis有一级缓存和二级缓存,底层都是用HashMap实现的
    key为CacheKey对象(后续说原因),value为从数据库中查出来的值。

    Mybatis的二级缓存模块是装饰器的典型实现,不清楚装饰者模式的看如下文章

    面试官:说一下装饰者模式的作用,以及哪些地方用到了装饰者模式吧

    画一个简易的装饰者模式类图
    在这里插入图片描述

    Component(组件):组件接口或抽象类定义了全部组件实现类以及所有装饰器实现的行为。

    ConcreteComponent(具体组件实现类):具体组件实现类实现了Component接口或抽象类。通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了Component接口中定义的最基本的功能,其他高级功能或后序添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。

    ConcreteDecorator(具体的装饰器):该实现类要向被装饰对象添加某些功能

    mybatis中caceh模块的类图
    在这里插入图片描述
    其中只有PerpetualCache是具组件实现类,提供了Cache接口的基本实现。而FifoCache
    ,LoggingCache等都是具体装饰者,在具体实现上加额外功能

    测试一级缓存

    测试的具体过程引用自参考博客

    github地址:https://github.com/kailuncen/mybatis-cache-demo

    接下来通过实验,了解MyBatis一级缓存的效果,每个单元测试后都请恢复被修改的数据。

    首先是创建示例表student,创建对应的POJO类和增改的方法,具体可以在entity包和mapper包中查看。

    CREATE TABLE `student` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
      `age` tinyint(3) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    

    在以下实验中,id为1的学生名称是凯伦

    实验1

    开启一级缓存,范围为会话级别,调用三次getStudentById,代码如下所示:

    public void getStudentById() throws Exception {
            SqlSession sqlSession = factory.openSession(true); // 自动提交事务
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            System.out.println(studentMapper.getStudentById(1));
            System.out.println(studentMapper.getStudentById(1));
            System.out.println(studentMapper.getStudentById(1));
        }
    

    执行结果:
    在这里插入图片描述
    我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。

    实验2

    增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。

    @Test
    public void addStudent() throws Exception {
            SqlSession sqlSession = factory.openSession(true); // 自动提交事务
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            System.out.println(studentMapper.getStudentById(1));
            System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "个学生");
            System.out.println(studentMapper.getStudentById(1));
            sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述
    我们可以看到,在修改操作后执行的相同查询,查询了数据库,一级缓存失效。

    实验3

    开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。(这个实验在原文上略有修改

    @Test
    public void testLocalCacheScope() throws Exception {
    	SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
    	SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
    
        StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
    	System.out.println("studentMapper1读取数据: " + studentMapper1.getStudentById(1));
    	System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    	System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "个学生的数据");
    	System.out.println("studentMapper1读取数据: " + studentMapper1.getStudentById(1));
    	System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    
    }
    

    输出如下

    DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
    DEBUG [main] - ==>  Preparing: SELECT id,name,age FROM student WHERE id = ? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    TRACE [main] - <==    Columns: id, name, age
    TRACE [main] - <==        Row: 1, 凯伦, 16
    DEBUG [main] - <==      Total: 1
    studentMapper1读取数据: StudentEntity{id=1, name='凯伦', age=16, className='null'}
    DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
    DEBUG [main] - ==>  Preparing: SELECT id,name,age FROM student WHERE id = ? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    TRACE [main] - <==    Columns: id, name, age
    TRACE [main] - <==        Row: 1, 凯伦, 16
    DEBUG [main] - <==      Total: 1
    studentMapper2读取数据: StudentEntity{id=1, name='凯伦', age=16, className='null'}
    DEBUG [main] - ==>  Preparing: UPDATE student SET name = ? WHERE id = ? 
    DEBUG [main] - ==> Parameters: 小岑(String), 1(Integer)
    DEBUG [main] - <==    Updates: 1
    studentMapper2更新了1个学生的数据
    DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
    studentMapper1读取数据: StudentEntity{id=1, name='凯伦', age=16, className='null'}
    DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
    DEBUG [main] - ==>  Preparing: SELECT id,name,age FROM student WHERE id = ? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    TRACE [main] - <==    Columns: id, name, age
    TRACE [main] - <==        Row: 1, 小岑, 16
    DEBUG [main] - <==      Total: 1
    studentMapper2读取数据: StudentEntity{id=1, name='小岑', age=16, className='null'}
    

    sqlSession1和sqlSession2读的时相同的数据,但是都查询了数据库,说明了一级缓存只在数据库会话层面共享

    sqlSession2更新了id为1的学生的姓名,从凯伦改为了小岑,但sqlSession1之后的查询中,id为1的学生的名字还是凯伦,出现了脏数据,也证明了之前的设想,一级缓存只在数据库会话层面共享

    一级缓存

    一级缓存的生命周期与SqlSession相同,如果你对SqlSession不熟悉,你可以把它类比为JDBC编程中的Connection,即数据库的一次会话。

    在这里插入图片描述

    要想了解缓存,就必须得了解一下Executor,这个Executor是干嘛的呢?你可以理解为要执行的SQL都会经过这个类的方法,在这个类的方法中调用StatementHandler最终执行SQL

    Executor的实现也是一个典型的装饰者模式

    在这里插入图片描述
    我相信你已经看出来,SimpleExecutor,BatchExecutor是具体组件实现类,而CachingExecutor是具体的装饰器。可以看到具体组件实现类有一个父类BaseExecutor,而这个父类是一个模板模式的典型应用,操作一级缓存的操作都在这个类中,而具体的操作数据库的功能则让子类去实现。

    至此终于搞明白了,一级缓存的所有操作都在BaseExecutor这个类中啊,看看具体操作

    query方法

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    

    当执行select操作,会先生成一个CacheKey,如果根据CacheKey能从HashMap中拿到值则放回,如果拿不到值则先查询数据库,从数据库中查出来后再放到HashMap中。追一下
    query方法就知道了,代码就不贴了,比较简单

    update方法

      @Override
      public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
      }
    

    当执行update操作时,可以看到会调用clearLocalCache()方法,而这个方法则会清空一级缓存,即清空HashMap

    总结

    1. MyBatis一级缓存的生命周期和SqlSession一致。
    2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
    3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement,即进行如下配置
    <setting name="localCacheScope" value="STATEMENT"/>
    

    原因也很简单,看BaseExecutor的query()方法,当配置成STATEMENT时,每次查询完都会清空缓存

       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    	// issue #482
    	clearLocalCache();
      }
    

    mybatis和spring整合的一些注意事项

    1. 在未开启事物的情况之下,每次查询,spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时的一级缓存是没有起作用的
    2. 在开启事物的情况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的

    CacheKey

    前面说到缓存的key是CacheKey对象,因为Mybatis中涉及动态SQL等多方面的因素,缓存的key不能仅仅通过String来表示,而是通过一个updateList,只有updateList的元素完全相同,则认为这2个CacheKey相同

    public class CacheKey implements Cloneable, Serializable {
    
      // 参与hash计算的乘数
      private final int multiplier;
      // CacheKey的hash值,在update函数中实时运算出来的,这些值都是为了方便更快的比较,具体可以看equals函数
      private int hashcode;
      // 校验和,hash值的和
      private long checksum;
      // updateList中的元素个数
      private int count;
      // 将判等的对象放到这个list中
      private List<Object> updateList;
    }
    

    CacheKey的其他属性都是为了加快比较的速度,具体可以看这个类的equals函数

    CacheKey的updateList放置了如下几个对象

    1. mappedStatment的id
    2. 指定查询结构集的范围
    3. 查询所使用SQL语句
    4. 用户传递给SQL语句的实际参数值

    怎么知道CacheKey是这些对象呢?你可以参考BaseExecutor的createCacheKey方法

      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
          }
        }
        if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }
    

    测试二级缓存

    测试的具体过程引用自参考博客

    二级缓存是基于namespace实现的,即一个mapper映射文件用一个缓存

    在本实验中,id为1的学生名称初始化为点点。

    实验1

    测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。

    @Test
    public void testCacheWithoutCommitOrClose() throws Exception {
            SqlSession sqlSession1 = factory.openSession(true); 
            SqlSession sqlSession2 = factory.openSession(true); 
            
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    }
    

    执行结果:
    在这里插入图片描述
    我们可以看到,当sqlsession没有调用commit()方法时,二级缓存并没有起到作用。

    实验2

    测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。

    @Test
    public void testCacheWithCommitOrClose() throws Exception {
            SqlSession sqlSession1 = factory.openSession(true); 
            SqlSession sqlSession2 = factory.openSession(true); 
            
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            sqlSession1.commit();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    }
    

    在这里插入图片描述
    从图上可知,sqlsession2的查询,使用了缓存,缓存的命中率是0.5。

    实验3

    测试update操作是否会刷新该namespace下的二级缓存。

    @Test
    public void testCacheWithUpdate() throws Exception {
            SqlSession sqlSession1 = factory.openSession(true); 
            SqlSession sqlSession2 = factory.openSession(true); 
            SqlSession sqlSession3 = factory.openSession(true); 
            
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
            StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
            
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            sqlSession1.commit();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
            
            studentMapper3.updateStudentName("方方",1);
            sqlSession3.commit();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    }
    

    在这里插入图片描述

    我们可以看到,在sqlSession3更新数据库,并提交事务后,sqlsession2的StudentMapper namespace下的查询走了数据库,没有走Cache。

    实验4

    验证MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。

    CREATE TABLE `student` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
      `age` tinyint(3) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    
    
    INSERT INTO `student` (`id`, `name`, `age`) 
    VALUES (1,'点点',16),(2,'平平',16),(3,'美美',16),(4,'团团',16);
    
    CREATE TABLE `class` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(20) COLLATE utf8_bin DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    
    INSERT INTO `class` (`id`, `name`) VALUES (1,'一班'),(2,'二班');
    
    CREATE TABLE `classroom` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `class_id` int(11) DEFAULT NULL,
      `student_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    
    INSERT INTO `classroom` (`id`, `class_id`, `student_id`)
    VALUES (1,1,1),(2,1,2),(3,2,3),(4,2,4);
    
    

    getStudentByIdWithClassInfo的定义如下

    <select id="getStudentByIdWithClassInfo" parameterType="int" resultType="entity.StudentEntity">
    	SELECT  s.id,s.name,s.age,class.name as className
    	FROM classroom c
    	JOIN student s ON c.student_id = s.id
    	JOIN class ON c.class_id = class.id
    	WHERE s.id = #{id};
    </select>
    

    通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。

    @Test
    public void testCacheWithDiffererntNamespace() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        SqlSession sqlSession3 = factory.openSession(true); 
        
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
            
        System.out.println("studentMapper读取数据: " + studentMapper.getStudentByIdWithClassInfo(1));
        sqlSession1.close();
        System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentByIdWithClassInfo(1));
    
        classMapper.updateClassName("特色一班",1);
        sqlSession3.commit();
        System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentByIdWithClassInfo(1));
    }
    

    执行结果:
    在这里插入图片描述

    在这个实验中,我们引入了两张新的表,一张class,一张classroom。class中保存了班级的id和班级名,classroom中保存了班级id和学生id。我们在StudentMapper中增加了一个查询方法getStudentByIdWithClassInfo,用于查询学生所在的班级,涉及到多表查询。在ClassMapper中添加了updateClassName,根据班级id更新班级名的操作。

    当sqlsession1的studentmapper查询数据后,二级缓存生效。保存在StudentMapper的namespace下的cache中。当sqlSession3的classMapper的updateClassName方法对class表进行更新时,updateClassName不属于StudentMapper的namespace,所以StudentMapper下的cache没有感应到变化,没有刷新缓存。当StudentMapper中同样的查询再次发起时,从缓存中读取了脏数据。

    实验5

    为了解决实验4的问题呢,可以使用Cache ref,让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的SQL操作都使用的是同一块缓存了。

    mapper文件中的配置如下

    <cache-ref namespace="mapper.StudentMapper"/>
    

    执行结果:

    在这里插入图片描述
    不过这样做的后果是,缓存的粒度变粗了,多个Mapper namespace下的所有操作都会对缓存使用造成影响。

    二级缓存的实现

    前面说了一级缓存的实现在BaseExecutor中,那么二级缓存的实现在哪呢?提示一下,前面提到的Executor。没错,就是CachingExecutor。下面详细介绍一下

    在这里插入图片描述

    二级缓存的相关配置有如下3个

    1.mybatis-config.xml

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

    这个是二级缓存的总开关,只有当该配置项设置为true时,后面两项的配置才会有效果

    从Configuration类的newExecutor方法可以看到,当cacheEnabled为true,就用缓存装饰器装饰一下具体组件实现类,从而让二级缓存生效

    // 开启二级缓存,用装饰器模式装饰一下
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    

    2.mapper映射文件中
    mapper映射文件中如果配置了<cache>和<cache-ref>中的任意一个标签,则表示开启了二级缓存功能,没有的话表示不开启

    <cache type="" eviction="FIFO" size="512"></cache>
    

    二级缓存的部分配置如上,type就是填写一个全类名,你看我上面画的图,二级缓存是用Cache表示的,一级缓存是用HashMap表示的。这就说明二级缓存的实现类你可以可以自己提供的,不一定得用默认的HashMap(对,二级缓存默认是用HashMap实现的),Mybatis能和Redis,ehcache整合的原因就在这

    这个eviction表示缓存清空策略,可填选项如下

    选项解释装饰器类
    LRU最近最少使用的:移除最长时间不被使用的对象LruCache
    FIFO先进先出:按对象进入缓存的顺序来移除它们FifoCache
    SOFT软引用:移除基于垃圾回收器状态和软引用规则的对象SoftCache
    WEAK弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象WeakCache

    可以看到在Mybatis中换缓存清空策略就是换装饰器。还有就是如果面试官让你写一个FIFO算法或者LRU算法,这不就是现成的实现吗?

    3.<select>节点中的useCache属性

    该属性表示查询产生的结果是否要保存的二级缓存中,useCache属性的默认值为true,这个配置可以将二级缓存细分到语句级别

    CachingExecutor利用了2个组件TransactionalCacheManager和TransactionalCache来管理二级缓存,为什么要多这2个组件呢?因为二级缓存不像一级缓存那样查询完直接放入一级缓存,而是要等事务提交时才会将查询出来的数据放到二级缓存中。

    因为如果事务1查出来直接放到二级缓存,此时事务2从二级缓存中拿到了事务1缓存的数据,但是事务1回滚了,此时事务2不就发生了脏读了吗?

    二级缓存的具体实现也不难,追一下CachingExecutor,TransactionalCacheManager,TransactionalCache就明白了,可以参考《Mybatis技术内幕一书》

    总结

    1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享
    2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻
    3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

    问题回答

    1. 一级缓存和二级缓存的生命周期分别是?
      一级缓存的生命周期是会话级别,因为一级缓存是存在Sqlsession的成员变量Executor的成员变量localCache中的。而二级缓存的生命周期是整个应用级别,因为二级缓存是存在Configuration对象中,而这个对象在应用启动后一直存在

    2. 同时配置一级缓存和二级缓存后,先查询哪个缓存?
      当然是先查询二级缓存再查询一级缓存啊,因为一级缓存的实现在BaseExecutor,而二级缓存的实现在CachingExecutor,CachingExecutor是BaseExecutor的装饰器

    参考博客

    [1]https://tech.meituan.com/2018/01/19/mybatis-cache.html
    [2]https://blog.csdn.net/u013887008/article/details/80379938
    [3]http://www.justdojava.com/2019/07/29/mybatis-secondcache/

    展开全文
  • 由于在最近的面试中,多次被问到Mybatis一级缓存二级缓存,因此在此进行总结,文章中的代码截图和最后的缓存执行顺序图摘抄自B站up主“狂神说”的Mybatis视频讲解,以下附上视频链接:...【Mybatis一级缓存和...

    由于在最近的面试中,多次被问到Mybatis的一级缓存和二级缓存,因此在此进行总结,文章中的代码截图和最后的缓存执行顺序图摘抄自B站up主“狂神说”的Mybatis视频讲解,以下附上视频链接:https://www.bilibili.com/video/BV1NE411Q7Nx?p=26

    【一】Mybatis的一级缓存和二级缓存
    1. 一级缓存: SqlSession级别,也叫本地缓存,默认开启,只要在同一个SqlSession中,执行相同的查询语句,并且查的是同一个mapper.xml文件,那么会走一级缓存,SqlSession会话关闭的话,一级缓存就失效了
      在这里插入图片描述

    上面的例子中,由于查询的都是id为1的用户,执行的sql语句也是一模一样的,并且是在同一个SqlSession中执行的,那么会走缓存
    在这里插入图片描述
    上面的例子中,第二次查询id为1的用户是不走缓存的,因为增删改操作都会导致缓存失效,即使改的数据跟我们查的数据无关

    一级缓存失效的情况:

    • 查询不同的东西(sql语句必须一模一样才会走缓存)
    • 增删改操作
    • 查询不同的Mapper.xml
    • 手动清理缓存(sqlsession.clearCache())
    • SqlSession会话连接关闭
    1. 二级缓存: 由于一级缓存作用域太低了,所以诞生了二级缓存,二级缓存是基于namespace级别的缓存,也就是一个命名空间,或者叫一个mapper.xml文件,对应一个二级缓存;开启二级缓存只需要在对应的mapper.xml文件声明一个 标签
      在这里插入图片描述
      上面这个例子应该是不走缓存的,虽然我们开启了二级缓存,但是由于二级缓存的工作机制是:SqlSession关闭时,一级缓存失效,才会将一级缓存中的东西放到二级缓存中,上面的例子由于sqlSession2执行查询的时候,sqlSession还未关闭,所以二级缓存中没有对应的缓存信息
      在这里插入图片描述
      如果是这个执行顺序的话,那么就会走缓存了

    二级缓存小结: 只要开启了二级缓存,在同一个Mapper.xml文件下就能生效;所有的数据都会先放在一级缓存中,只有当会话提交,或者会话关闭的时候,才会提交到二级缓存中

    1. 缓存的执行顺序
      在这里插入图片描述
    展开全文
  • Mybatis一二级缓存

    多人点赞 2020-07-11 16:57:47
    面试官:虫虫你简历上写了了解mybatis缓存,那你能说说一级缓存和二级缓存的区别吗? 虫虫:我只知道这是用来缓存sql查询的数据 面试官:没了? 虫虫:没了 面试官:公司门知道在哪里吧 自己走还是我送你 以上是虫虫的...

    在这里插入图片描述

    前言

    面试官:虫虫你简历上写了了解mybatis缓存,那你能说说一级缓存和二级缓存的区别吗?



    虫虫:我只知道这是用来缓存sql查询的数据



    面试官:没了?



    虫虫:没了



    面试官:公司门知道在哪里吧 自己走还是我送你

    以上是虫虫的面试经历 于是虫虫决定恶补一下Mybatis缓存机制的知识
    image

    Mybatis的缓存,包括一级缓存和二级缓存

    Mybatis对缓存提供支持,一级缓存是默认使用的



    二级缓存需要手动开启



    区别:一级缓存的作用域是一个sqlsession内;二级缓存作用域是针对mapper进行缓存.

    一级缓存:

    在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

    image

    一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。



    二级缓存:

    二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
    image

    1、在配置文件中 开启二级缓存的总开关

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

    2、 在mapper映射文件中开启二级缓存

    <cache eviction="FIFO" flushInterval="60000" size="512" 
    readOnly="true"/>
    
    参数名属性
    eviction收回策略
    flushInterval刷新间隔
    size引用数目
    readOnly只读
    关于eviction的各个参数属性:
    参数名属性
    eviction=“LRU”最近最少使用的:移除最长时间不被使用的对象。 (默认)
    eviction=“FIFO”先进先出:按对象进入缓存的顺序来移除它们。
    eviction=“SOFT”软引用:移除基于垃圾回收器状态和软引用规则的对象。
    eviction=“WEAK”弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    3、实体类实现Serializable

    禁用缓存

    如测试sql语句性能时缓存会影响测试准确性 需要禁用
    在映射文件中:
    默认值是true useCache=”false”

    <select id="findAllPets" resultMap="petsMap" useCache="false">
     select * from pets
    </select>
    

    刷新缓存

    在映射文件中:
    属性:flushCache=”true”

    刷新缓存,在查询语句中,默认值是false,在新增删除修改语句中,默认值是true(清空缓存)

    image


    要是能为您提供帮助,请给予支持(关注、点赞、分享),虫虫蟹蟹大家了!
    展开全文
  • 面试官:虫虫你简历上写了了解mybatis缓存,那你能说说一级缓存和二级缓存的区别吗? 虫虫:我只知道这是用来缓存sql查询的数据 面试官:没了? 虫虫:没了 面试官:公司门知道在哪里吧 自己走还是我送你 Mybatis的...
  • Mybatis的査询缓存分为一级缓存二级缓存一级缓存是 SqlSession別的缓存二级缓存是mapper级别的缓存二级缓存是多个 SqlSession共享的。 Mybatis通过缓存机制减轻数据压力,提高数据库性能。 一级缓存( ...
  • Mybatis一级缓存是默认开启的,它只相对于同个SqlSession有效,所以也称之为SqlSession缓存。当参数和SQL完全相同的情况下,使用同个SqlSession对象调用同个Mapper方法,当第1次执行SQL语句后,MyBatis会...
  • Java面试Mybatis一级缓存二级缓存的区别? 两者区别:一级缓存的作用域是在SqlSession中,二级缓存的作用域是针对mapper做缓存一级缓存(本地缓存): 一级缓存是框架默认为我们开启的,我们不需要做任何...
  • 介绍 又到了一年面试季,所以打算写一点面试常问的东西,...Mybatis一级缓存二级缓存的工作原理,会遇到什么问题? 一级缓存二级缓存的生命周期分别是? Mybatis和Spring整合后,一级缓存为什么会失效? 同...
  • mybatis一级缓存二级缓存是经常出现在面试中的一道题目,想要区分一级缓存二级缓存首先要了解mybtis中比较重要的几个类的生命周期。 SqlSessionFactory SqlSessionFactory是mybatis启动时读取所有配置文件后...
  • 春招正在火热进行中,对于Mybatis一二级缓存问题,你能完美地回答给面试官吗?关于MyBatis的一级缓存和二级缓存,你应该了解这些
  • 1.mybatis中的缓存是在mybatis框架中的Executor中来...(1)BaseExecutor(用来存储我们的一级缓存) @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
  • mybatis一二级缓存

    2018-08-08 20:52:23
    1.缓存 查询数据时将查询结果存放到内存(缓存区)中。 每次查询数据时,先判断缓存区中是否存在数据, 如果存在,就从缓存区中... 失败的一级缓存测试 ※面试相关 [1]SQL语句或查询条件不同 [2]分属不同S...
  • 到 https://tech.meituan.com/2018/01/19/mybatis-cache.html “在上文中提到的一级缓存中,其最大的共享范围就是个SqlSession内部”
  • 面试中都会问起mybatis一级缓存二级缓存,它体现出你对mybatis这个开发中的理解,如果照着答案背的话只能拿到个及格分,所以今天咱们就好好聊聊mybatis。 另外本人整理了20年面试题大全,包含spring、并发、...
  • MyBatis 二级缓存详解

    2019-08-08 09:10:00
    我们在上篇文章介绍了 MyBatis一级缓存的作用,如何开启,一级缓存的本质是什么,一级缓存失效的原因是什么?MyBatis 只有一级缓存吗?来找找答案吧!MyBa...
  • mybatis 面试一级缓存二级缓存

    千次阅读 2020-02-20 17:14:45
    mybatis 现在是面试必问的,其中最主要的除了一些启动流程,基础语法,那么就是缓存问题了,在面试中也是常问的问题之; 大家都知道mybatis是有二级缓存的, 其中一级缓存默认是开启的,二级缓存是要手动配置开启...
  • 1. 介绍 使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验。 使用redis做mybatis二级缓存可是内存可控&lt;如将单独的服务器部署出来用于二级缓存&gt;,管理方便。 2. 使用思路 a. 配置...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,721
精华内容 10,688
关键字:

mybatis一二级缓存面试