• 【Myatis】mybatis的缓存机制


    1、mybatis的代理详解

    Java中动态代理主要是JDK动态代理(接口),CGLib动态代理(类继承)

          SqlSession sqlSession = sqlSessionFactory.openSession();
    
    
            /**
             *在通过sqlSession.getMapper可以获取一个代理对象
             * 对于StudentMapper并没有显性的实现该接口的实现类,
             * 该对象是在运行时动态产生的,该对象就是动态代理对象
             *
             * JDK动态代理
             * 1、首先存在接口(StudentMapper.java)
             * 2、必须存在该接口的实现类(sqlSession.selectOne)
             * 3、实现invocationHandler辅助类
             * 4、通过Proxy类来产生代理对象
             */
    
            //单个对象原始调用方式
    //        Student student = sqlSession.selectOne("com.tulun.Mybatis.mapper.StudentMapper.selectStudentById", 4);
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            Student student = studentMapper.selectStudentById(4);
            System.out.println(student);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    mybatis是否用到了JDK动态代理相关的接口和类?产生代理对象如何组织?

    mybatis中代理对象的获取是通过如下方法:

     sqlSession.getMapper(StudentMapper.class);
    
    • 1

    mapper接口是如何添加进去的?
    通过Java代码形式来进行数据源、映射文件的配置形式如下:

            Configuration configuration = new Configuration();
            PooledDataSourceFactory dataSourceFactory = new PooledDataSourceFactory();
            DataSource dataSource = dataSourceFactory.getDataSource();
            JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
            //设置环境
            Environment environment = new Environment("test", jdbcTransactionFactory, dataSource);
            configuration.setEnvironment(environment);
    
            //设置mapper接口
            configuration.addMapper(StudentMapper.class);
            new SqlSessionFactoryBuilder().build(configuration);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    重点在于接口的添加形式:

    configuration.addMapper(StudentMapper.class);
    
    • 1

    在Configuration类中:

      public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
      }
    
    • 1
    • 2
    • 3

    mapper在Configuration类中没有做任何事,只是添加到mapperRegistry类中。

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {//只允许添加接口
          if (hasMapper(type)) {//不允许重复添加
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行的configuration.addMapper(StudentMapper.class),实际上最终被放入HashMap中,其命名knownMappers,它是mapperRegistry类中的私有属性,是一个HashMap对象,key是接口class对象,value是MapperProxyFactory的对象实例。

    通过getMapper获取代理对象。
    DefaultSqlSession类:

      public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
      }
    
    • 1
    • 2
    • 3

    configuration类中存放所有的mybatis全局配置信息,从参数上可以知道class类型。
    configuration类:

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    
    • 1
    • 2
    • 3

    configuration类中getmapper调用了mapperRegistry.getMapper,mapperRegistry中存放一个HashMap

    mapperRegistry类:

     private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    
    
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    调用mapperRegistry类中的getMapper方法,该方法中会到hashmap中通过类名获取对应的value值,是MapperProxyFactory对象,然后调用newInstance方法,该方法是创建了一个对象。

    public class MapperProxyFactory<T> {//映射器代理工厂
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
          //使用的是JDK自带的动态代理对象生成代理类对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    //实现了InvocationHandler接口
    
      @Override
        //代理之后,所有的mapper的方法调用,都会调用这个invoke方法
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (method.isDefault()) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    mybatis中getmapper的代理对象的获取,使用的是JDK动态代理,在MapperRegistry类中的HashMap中存放了所有的mapper接口,key是接口的类名信息,value是MapperProxyFactory类型实例,getmapper操作会是当前对象调用newInstance操作,该操作会调用Proxy.newProxyInstance核心在于调用InvocationHandler接口,实现了invoke方法,除了调用原始的接口调用,而且还对调用进行增强,进行了方法缓存,且最终会执行增删改查操作。

    总结Mapper接口方法执行的几个点:

    • Mapper接口初始在SQLSessionFactory注册的。
    • Mapper接口注册在MapperRegistry类的HashMap中,key是mapper的接口类名,value是创建当前代理工厂。
    • Mapper注册之后,可以从过SQLSession来获取get对象。
    • SQLSession.getMapper运用了JDK动态代理,产生了目标Mapper接口的代理对象,动态代理的代理类是MapperProxy,实现了增删改查调用。

    2、mybatis的缓存机制

    2.1、缓存介绍

    mybatis中提供查询缓存,用于减轻数据库的压力,提高数据库的性能。
    mybatis提了了一级缓存和二级缓存:
    在这里插入图片描述
    一级缓存是SQLSession级别缓存,在操作数据库时都需要构造SQLSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SQLSession之间的缓存数据区域是互不影响的。
    一级缓存的作用于是在同一个SQLSession,在同一个SQLSession中执行相同的两次SQL,第一次执行完毕在后会将数据写到缓存中,第二次从缓存中进行后去就不在数据库中查询,从而提高了效率,
    当一个SQLSession结束后该SQLSession中的一级缓存也就不存在了,mybatis默认开启一级缓存。

    二级缓存是mapper级别缓存,多个SQLSession去操作同一个mapper的SQL语句,多个SQLSession操作都会存在二级缓存中,多个SQLSession共用二级缓存,二级缓存是跨SQLSession的。
    二级缓存是多个SQLSession共享的,作用域是mapper下的同一个namespace。不同的SQLSession两次执行相同的namespace下的SQL最终能获取相同的SQL语句结果。
    mybatis默认是没有开启二级缓存的,需要在全局配置文件中配置开启二级缓存。

    2.2、一级缓存

    一级缓存介绍

    在这里插入图片描述

    每个SqlSession中持有一个执行器Executor,每个执行器中有一个Local Cache,当用户发起查询时,mybatis根据当前执行的语句生成mapperStatement,在Local Cache中进行查询,如果缓存命中的话,直接返回给用户,如果没有命中,查询数据库,结果写入Local Cache中,最后返回给用户。

    通过ID查询用户信息:

    在这里插入图片描述

    缓存生效失效时机:
    如果是连续的查询同一个数据操作,在第一次查询之后,后续查询都可以命中缓存。
    如果在查询之后,紧接着是进行变更操作,就会导致缓存失效。

    一级缓存测试

    mybatis是默认支持一级缓存,不需要做其他配置。

    测试1:同一个SQLSession下连续进行查询操作:

            //同一个SQLSession连续进行查询操作
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            //第一次查询操作
            System.out.println("第一次查询开始......");
            Student student = studentMapper.selectStudentById(4);
            System.out.println("第一次查询结束......");
    
            System.out.println("第二次查询开始......");
            Student student1 = studentMapper.selectStudentById(4);
            System.out.println("第二次查询结束......");
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过执行结果可知:第一次查询会查询数据库,第二次查询操作是没有查询SQL,即通过缓存查询到结果返回。

    测试2:第一次查询结束后,对数据进行变更操作,再次执行查询操作:

            //同一个SQLSession连续进行查询操作
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            //第一次查询操作
            System.out.println("第一次查询开始......");
            Student student = studentMapper.selectStudentById(4);
            System.out.println("第一次查询结束......");
    
            //对数据进行变更操作
            System.out.println("变更操作开始....");
            studentMapper.updateNameById(4,"test");
            sqlSession.commit();
            System.out.println("变更操作结束....");
    
            //第二次查询
            System.out.println("第二次查询开始......");
            Student student1 = studentMapper.selectStudentById(4);
            System.out.println("第二次查询结束......");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在查询结束后,如果对于数据进行变更操作,会删除掉缓存,导致第二次查询依然需要进入到数据库。

    测试3:不同的SQLSession下同一个操作是否会命中缓存:

    SqlSession sqlSession = sqlSessionFactory.openSession();
            //分别数据不同的SQLSession是否会命中缓存
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            //第一次查询操作
            System.out.println("第一次查询开始......");
            Student student = studentMapper.selectStudentById(4);
            System.out.println("第一次查询结束......");
    
            SqlSession sqlSession1 = sqlSessionFactory.openSession();
            StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
            //第二次查询
            System.out.println("第二次查询开始......");
            Student student1 = studentMapper1.selectStudentById(4);
            System.out.println("第二次查询结束......");
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    不同的SQLSession的一级缓存是无法共享的。

    2.3、二级缓存

    mybatis中二级缓存是mapper级别的缓存,默认是关闭的。
    对于一个mapper下的不同的SQLSession可以共享二级缓存。
    不同的mapper是相互隔离的。
    二级缓存的特点是需要打开二级缓存的配置,并且需要映射的java类需要实现序列化。

    二级缓存原理

    在这里插入图片描述

    二级缓存和一级缓存的区别:
    二级缓存范围更大,多个SQLSession可以共享一个mapper级别的二级缓存,数据类型依然是HashMap来存储二级缓存内容,mapper是按照namespace划分,如果namespace相同则使用同一个二级缓存,
    一级缓存范围会更小,是一个SQLSession级别。

    二级缓存使用步骤
    第一步:在mybatis的全局配置文件中开启二级缓存:

        <settings>
            <!--cacheEnabled:开启mybatis的二级缓存-->
            <setting name="cacheEnabled" value=" true"/>
        </settings>
    
    • 1
    • 2
    • 3
    • 4

    第二步:将映射的pojo类实现序列化:

    public class Student implements Serializable
    
    • 1

    第三步:在mapper配置文件中使用cache标签:

    <!--
        cache和二级缓存相关标签
        eviction属性:代表缓存的回收策略,mybatis提供了回收策略如下:
        LRU:最近最少使用,对于最长时间不用的对象进行回收
        FIFO:先进先出,按照帝乡进入缓存的顺序来进行回收
        SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
        WEAK:弱引用,基于垃圾回收器状态和弱引用规则的对象
    
        flushInterval属性:刷新间隔时间,单位是毫秒
        size属性:引用数目,代表缓存可以存储最多多少个对象
        readOnly属性:只读,意味着缓存数据只能读取不能修改
        -->
        <cache eviction="FIFO" flushInterval="1000" size="1024" readOnly="false"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注:在select查询导致上使用属性:
    useCache=“true” 缓存的使用和禁止
    flushCache=“true” 刷新缓存

    二级缓存使用测试

        //二级缓存的测试
        @Test
        public void testCache2() {
            //不同的SQLSession会话进行相同的SQL查询操作
            //SQLSession1实例
            SqlSession sqlSession = sessionFactory.openSession();
            Student23Mapper student23Mapper = sqlSession.getMapper(Student23Mapper.class);
            //SQLSession2实例
            SqlSession sqlSession1 = sessionFactory.openSession();
            Student23Mapper student23Mapper1 = sqlSession1.getMapper(Student23Mapper.class);
            //sqlsession实例3
            SqlSession sqlSession2 = sessionFactory.openSession();
            Student23Mapper student23Mapper2 = sqlSession2.getMapper(Student23Mapper.class);
    
    
            //第一次查询id为1的用户
            Student23 student23 = student23Mapper.selectStudentByUid(1L);
            System.out.println(student23);
            //这里执行关闭操作,将SQLSession中的数据写入到二级缓存区域
            sqlSession.close();
    
    
    
            //第二次查询id为1的用户
            Student23 student24 = student23Mapper1.selectStudentByUid(1L);
            System.out.println(student24);
            sqlSession1.close();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    表明不同的SQLSession可以共享一个二级缓存。

    底层:二级缓存是一个Cache接口的实现类,一级缓存是一个localCache是一个HashMap的属性,
    执行过程先经过二级缓存,在二级缓存未命中时才会走一级缓存。

  • 相关阅读:
    GEE数据融合——Landsat (collection 2,level 2 )4、5、7、8、9长时间序列影像数据融合和视频导出分析
    SQLI手动注入和python sqlmap代码注入
    FFmpeg入门详解之122:Qt5 FFmpeg本地摄像头采集预览实战
    Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第7章 Vue.js高级进阶 7.8 slot插槽
    vue脚手架vue-cli可视化安装与介绍看完觉得很简单!
    C语言:指针的应用
    EtherCAT主站转Ethernet/IP网关
    荧光染料AF488 carboxylic acid,AF488 COOH/ACID/羧酸羧基
    荐读 | 图计算的查询模式有哪些?
    安全:Linux重要安全配置之关闭常规ssh链接-开启密钥方式链接-防入侵非常重要以及有效的一项操作
  • 原文地址:https://blog.csdn.net/weixin_49782150/article/details/125893277