• Mybatis搞两下(sqlsession,动态代理)


    本文主要介绍和,其中动态代理实操了源码,我觉得值得一看,能加深你对Mybatis底层文件的映射,代理类进行的CRUD的操作,即使他本身也是通过SqlSession来执行增删改查的操作。

    SqlSession详解

    每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

    SqlSession创建流程:

    这里出现了很多个对象, 包括但不限于SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession等,这些对象的生命周期和作用域顺便说一下

    SqlSessionFactoyBuilder

    SqlSessionFactoryBuilder这个类的作用就是为了创建SqlSessionFactory的,一旦SqlSessionFactory创建完毕,SqlSessionFactoryBuilder就没有存在的价值了,就应该被销毁。所以SqlSessionFactoryBuilder 最好的作用域就是方法体内(及作为一个本地方法变量) ,用完即销毁。生命周期也就是调用方法的开始到结束。

    SqlSessionFactory

    ​ SqlSessionFactory可以被认为是一个数据库连接池,它的作用是创建SqlSession接口对象。因为MyBatis的本质就是Java对数据库的操作,所以SqlSessionFactory的生命周期在于于整个MyBatis的应用之中,所以 一旦创建了SqlSessionFactory的生命周期就等同于MyBatis的应用周期 。

    由于SqlSessionFactory是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据资源的控制,也会导致连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望SqlSessionfactory作为一个单例,让它在应用中不共享。

    SqlSession

    ​ 刚才说SqlSessionFactory可以看成数据库连接池,那么SqlSession就相当于一个数据库连接(Connection对象),你可以在一个事务里面执行多条SQL,然后通过它的commit、rollback等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给SqlSessionFactory,否则数据库资源就很快被消耗精光,系统应付瘫痪,所以用try...catch...fanally语句来保证其正确关闭。

    每个线程都应该有自己的SqlSession实例。 SqlSession 的实例不是线程安全的,因此是不能被共享的 ,所以它的 最佳的作用域是请求或方法作用域 。

    Mapper

    Mapper是一个接口,它由SqlSession所创建,所以它的最大生命周期至多和SqlSession保持一致,尽管它很好用,但是由于SqlSession关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于SqlSession的生命周期。Mapper代表是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

    Mybatis的生命周期图

    具体实战这里不做赘述,详情见上一篇博客,本篇继续磕理论。

    浅谈Mybatis的动态代理

    相信很多人跟我一样,每次用Mybatis的时候,都知道怎么用,不就是定义×××Mapper接口类,并利用它来实现CRUD操作,那×××Mapper.java和×××Mapper.xml到底怎么关联起来的?

    这一个java一个xml,在不装插件的情况下,就像八竿子打不着的两个文件,咱也不能跳,也不能对应,很难想象他们有千丝万缕的关系。

    Mapper插件

    这里顺便推荐一个IDEA里的插件,可以在java和xml里对应的Mapper方法进行跳转

    讲了一些题外话,来看看Mybatis利用动态代理的技术帮我们生成代理类的具体实现机制

    MyBatis动态代理实现

    //根据id查询用户
        @Test
        public void getUserById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);
            System.out.println(user);
            sqlSession.close();
        }

    我先从具体实现类里截出一段代码,可以看到这里我们是用sqlSession.getMapper()方法或得了UserMapper的对象,但是我们实际上是获取了UserMapper接口的代理类,然后再由代理类执行的方法。那代理类如何生成的?先看看SqlSessionFactory工厂创建的过程,比如读取mybatis-config配置文件,读取我们的Mapper.xml文件等。

    mybatis-config配置文件读取

    这里用new SqlSessionFactoryBuilder().build(inputStream)来创建sqlSessionFactory工厂,我们点进build的源码康康

    可以看到对于配置文件的解析,调动三参build方法,具体解析代码又在这个build方法里,

    继续套娃,还是调用了parse()方法,

    不难发现,这个parseConfiguration方法源码里都是对mybatis全局配置文件里各个标签元素的解析,最后返回一个Configuration对象,这个对象包含了Mybatis所有的配置信息了。

    概括一下,他是从XMLConfigBuilder里构建,Mybatis通过XMLConfigBuilder读取mybatis-config.xml中的配置信息,保存在Configuration中。

    Mapper.xml读取

    别忘了mapper的映射也是在mybatis-config.xml里配置的哦,所以有没有眼尖的发现,mapper文件的读取其实已经出现在刚才上面某张图里啦,

    解析mapper映射文件

    mapperElement(root.evalNode("mappers"));

    咱也不墨迹了,进去看看吧

    来了来了,parse()他又来了,跟上面配置文件里的是不是很像?点进去得到这个方法的完整信息。

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
            //这里是解析mapper元素的方法
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
            //这里根据namespace属性来生成动态代理类
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }

    可以看到configurationElement方法在这里很关键,我们打个断点直接进去康康。

    由于今天讲的是动态代理,这里buildStatementFromContext解析select|insert|update|delete这些操作就不细看了,我们发现configurationElement首行就拿下了namespace的信息,存储在namespace里,然后存在了builderAssistant对象里。

    回到上一步parse()中的最后一步,bindMapperForNamespace()里,他有没有去找builderAssistant去拿namespace对象呢?我们带着好奇心去找他对质一下,我怀疑他肯定摸了。

    动态代理来了

    bindMapperForNamespace()源码去一下

    你看他摸了,他真的摸了。好了不开玩笑了,这里显然是代理最重要的部分,拿出来康康。

    private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        //获取mapper里的namespace对象
        if (namespace != null) {
          Class boundType = null;
          try {
              //获取namespace属性对应的Class对象,存在boundType(绑定类型)里
            boundType = Resources.classForName(namespace);
          } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
              //如果classnotfind,就是如果没有,我们就忽略就行了,上面一行源码里的注释也可以发现,绑定类型不是非必须的
          }
          if (boundType != null) {
              //先判断是否加载
            if (!configuration.hasMapper(boundType)) {
              // Spring may not know the real resource name so we set a flag
              // to prevent loading again this resource from the mapper interface
              // look at MapperAnnotationBuilder#loadXmlResource
              configuration.addLoadedResource("namespace:" + namespace);
              //如果有对应类,就调用add*方法把他添加到configuration里
                configuration.addMapper(boundType);
            }
          }
        }
      }

    我们发现这里最后一步把绑定对象直接调用addMapper方法丢到Configuration类里了,大家想想这个addMapper和之前测试类里的getMapper一样?

    千呼万唤始出来,来吧来吧

    public class Configuration {
        *********
    
    public  void addMapper(Class type) {
        mapperRegistry.addMapper(type);
      }
    
      public  T getMapper(Class type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
        ***********
    }

    这里面的getMapper方法,将工作继续交到MapperRegistry的getMapper的方法中,要找到真正的代理类,我们还得往下走,他调用了mapperRegistry的getMapper方法,

    进!

    @SuppressWarnings("unchecked")
      public  T getMapper(Class type, SqlSession sqlSession) {
        final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
        }
      }

    看到这里终于看到一个眼熟的东西了,proxy是啥?是不是我们一直找的东西,你瞅mapperProxyFactory啥意思

    咱也能猜到,这是通过这个工厂来生成我们Mapper映射器的代理类吧

    往下看,工厂是不是newInstance(sqlSession)了啊,那咱进去看看呗

    @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }

    根据参数类型发现,这里调用了第二个newInstance方法,创建了一个MapperProxy对象,然后又作为参数,返回到第一个方法,最后根据这个对象创建的代理类并返回,到这一步其实该拿的都拿到了,代理类已经啥都有了。

    但是好像跟我们了解的JDK动态代理好像不太一样?InvocationHandler呢?invoke()呢?咱再找找。仔细一看,不就在这里嘛

    当然是这个代理类实现的InvocationHandler接口,重写的invoke()方法了。

    可以看到invoke最后调用execute

    再进execute,发现神奇的东西来了,MapperMethod里的execute里真是啥都有,咱的CRUD操作全都封装在这里,sqlSession接口调用的最后也来到了这里,和我们自己实现的UserDao接口里直接用的SqlSession实现类的方法是一样的,这就是动态代理实现的全过程了。

    感觉讲的还不是很清楚,画个getMapper方法的流程图吧

    我们通过SqlSession的getMapper方法获得接口代理来进行CRUD操作,其底层走的还是SqlSession的使用方法

    再来一个总体的流程图

  • 相关阅读:
    Maven项目package为jar包后在window运行报A JNI error has occurred
    Docker Tomcat 搭建文件服务器
    linux 下使用 sar -n 命令查看Kbps、bps的带宽速率
    【影刀演示_发送邮件的格式化HTML留存】
    Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc
    考研数据结构大题整合_组三(LZH组)
    Three.js如何计算3DObject的2D包围框?
    论文复现《SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM》
    合作技术保密协议
    victoriaMetrics无法获取抓取target的问题
  • 原文地址:https://blog.csdn.net/YYniannian/article/details/126057251