自由书

V1

2022/04/05阅读:11主题:全栈蓝

一文搞定Mybatis代理

最近不是金三银四嘛,传说中的卖身旺季。这不就有朋友出去技术切磋。其中就有提到了Mybatis框架,被问到Mybatis的 Mapper 都是没有实现类接口类,那是如何找到并解析xml中的sql内容的。相信这个问题在你面前肯定是小菜一碟对吧,但我想应该还是有一些涉世未深的小伙伴有点迷糊,所以今天就拿来絮叨絮叨。

提到Mybatis,咱们也就不要跳开Spring老大哥了,而Mybatis和Spring的基情也是摆在台面上的,也就是MyBatis-Spring。作为两个框架的桥梁,可以把Mybatis的事务转换为Spring管理的事务,可以把Mybatis的异常也转给了Spring来管理异常,把Mybatis和Spring紧密无缝的关联在一起了。

好了开头的废话就到这儿,为了深入了解Mybatis的Sql解析机制,我们建一个简单的工程,把Mybatis跑起来。

环境准备

搭建项目不是我们主要的方向,我就跳过了搭建项目的过程了,小伙伴直接获取源码来使用就行,看我配置的Mybatis吧,以下是核心配置,我们就从sqlSessionFactory()这个方法开始探索

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources(mapperLocation);
    factoryBean.setMapperLocations(resources);
    return factoryBean.getObject();
}

private DataSource dataSource() {
    DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
    dataSourceBuilder.username(userName);
    dataSourceBuilder.password(password);
    dataSourceBuilder.driverClassName(driverClassName);
    dataSourceBuilder.url(url);
    DataSource build = dataSourceBuilder.build();
    return build;
}

熟悉Spring的小伙伴可能一眼就看到SqlSessionFactoryBean这个工厂类,这个类是实现了FactoryBean接口的,MyBatis-Spring的实现就是从这里切入的。后续专门出一篇文章来分析FactoryBean,请关注 Java极客帮

初始化Mybatis的关键

回到SqlSessionFactoryBean这类个身上,由于他是个工厂类,所以Spring在创建这个类时返回的并不是本身,而是getObject() 方法的返回结果。那我们就深入SqlSessionFactoryBean看看是如何实现的呢?

private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null"Property 'configuration' and 'configLocation' can not specified with together");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

看到上面的判断框架判断了dataSource不为空,configuration或者configLocation不能同时设置,所以我们在sqlSessionFactory()方法是指定了dataSource和mapperLocations,其实mapperLocations也可以不设置,因为可以使用@Selelct、@Delete、@Insert、@Update等注解的,所以构建SqlSessionFactory对象的必要条件是dataSource。

spring集成mybatis初始化其实就是从这里开始的
this.sqlSessionFactory = this.buildSqlSessionFactory();

这个方法是初始化mybatis的开始,其实和原生的mybatis已经没啥区别的,但是我们本文要分析Mapper接口,所以我们就找到Mapper解析的地方

解析Mapper路径以及包装存放
解析Mapper路径以及包装存放

看到这里重点对象MapperProxyFactory已经出现,为了篇幅MapperProxyFactory的代码我就不贴了,聪明的小伙伴已经打开电脑去看代码去了,其实代码也不难,就是用了Jdk的动态代理啦。因为代理的是Interface接口嘛。

回归重点

我们使用spring集成mybatis 还有个很重要的配置是配置@MapperScan注解或者配置MapperScannerConfigurer。@MapperScan注解方式其实也是通过构建MapperScannerConfigurer然后SpringIOC容器添加实现的。那我们关注MapperScannerConfigurer的内部。

MapperScannerConfigurer类关系图
MapperScannerConfigurer类关系图

看到这个类关系图还是偏简单的,主要用到的Spring的Bean生命周期。我们继续跟踪postProcessBeanDefinitionRegistry()

包装Mapper的Bean对象
包装Mapper的Bean对象

经过processBeanDefinition方法处理之后你会发现我们看到的Bean已然不是原来的Bean了

经过包装的Mapper
经过包装的Mapper

看到这个其实伙伴们心里也明白了,我们注入的Mapper实际上是从MapperFactoryBean这个工厂类生产的对象,因为Spring在注入时调用了工厂类的getObject()方法,而MapperFactoryBean的getObject是从MapperRegistry类的局部变量knownMappers中取出的MapperProxyFactory对象然后调用MapperProxyFactory的newInstance()实例化方法出来的代理对象,就是使用Jdk动态代理构建的。

Mybatis的Mapper调用

上面梳理清楚了Spring注入的Mapper实际上是MapperProxyFactory代理的对象,那么真正使用的时候是怎么个逻辑呢?

当XXMapper.method()被调用时,就会调用代理对象的org.apache.ibatis.binding.MapperProxy#invoke方法上,进而执行到org.apache.ibatis.binding.MapperMethod#execute ,然后根据最开始初始化Mybatis时存储的MappedStatement找到对应SQL的type执行不同的逻辑,Mybatis执行流程就不在这里赘述了。

下次遇到这类问题知道怎么回答了吗,不要一句话使用代理就完了。如果觉得对你有帮助的话请关注下吧 Java极客帮

分类:

后端

标签:

后端

作者介绍

自由书
V1