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

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

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

SqlSession详解

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

SqlSession创建流程:

image-20200922142305914

这里出现了很多个对象, 包括但不限于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语句来保证其正确关闭。

image-20200922145535045

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

Mapper

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

Mybatis的生命周期图

image-20200922145050791

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

浅谈Mybatis的动态代理

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

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

image-20200922151848050

Mapper插件

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

image-20200922152255534

讲了一些题外话,来看看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配置文件读取

image-20200922153038524

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

image-20200922153456282

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

image-20200922153714450

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

image-20200922153843704

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

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

Mapper.xml读取

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

image-20200922155617596

解析mapper映射文件

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

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

image-20200922155947272

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

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

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

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

image-20200922161057724

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

image-20200922161531281

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

动态代理来了

bindMapperForNamespace()源码去一下

image-20200922161816870

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

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一样?

image-20200922165011817

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

public class Configuration {
    *********

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
    ***********
}

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

进!

@SuppressWarnings("unchecked")
  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);
    }
  }

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

image-20200922165516671

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

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

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

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

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

image-20200922171109227

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

image-20200922171303442

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

可以看到invoke最后调用execute

image-20200922171826683

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

image-20200922171914244

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

image-20200922173346985

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

再来一个总体的流程图

image-20200922190922895

参考博客&鸣谢

https://www.cnblogs.com/hopeofthevillage/p/11384848.html

https://segmentfault.com/a/1190000019130222

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上上签i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值