MyBatis 执行流程及源码解析

我们在日常工作中广泛使用mybatis作为数据持久层框架,但是mybatis的执行流程是怎么样的,你了解过吗。本文将从源码角度,带你分析mybatis的工作原理。

先看一个简单的例子,以Service调用Mapper接口为例:

public interface StudentMapper {
    @Select("select * from student")
    public List> query();
}
@Service("studentService")
public class StudentServiceImpl implements StudentService {
    @Autowired
    StudentMapper studentMapper;

    @Override
    public List> query() {
        return studentMapper.select();
    }
}

向Service中注入这个Mapper并调用时,你知道这时注入的是什么吗?

通过调试,可以知道这时实际的studentMapper是一个类型为MapperProxy的代理对象,下面将从myabtis环境初始化开始,具体分析代理对象的产生过程。(不熟悉代理对象的同学,可以查看之前专门讲java代理的文章)

 

一、配置SqlSessionFactoryBean 时都做了什么?

在进行spring和mybatis整合时,会用xml或者注解的方式去配置一个SqlSessionFactoryBean,本文中以注解方式为例:

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
   SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
   sqlSessionFactoryBean.setDataSource(dataSource);
   return  sqlSessionFactoryBean;
}

看一下SqlSessionFactoryBean的继承实现关系:

MyBatis 执行流程及源码解析_第1张图片

先看spring中两个非常重要的接口,FactoryBean和InitializingBean。

FactoryBean:

FactoryBean是一个spring中比较特殊的Bean,通过它的getObject()方法可以返回一个对象实例。SqlSessionFactoryBean中getObject()方法的实现:

MyBatis 执行流程及源码解析_第2张图片

在这里用于创建并返回一个SqlSessionFactory,在 spring +mybatis 的环境下,我们使用SqlSessionFactoryBean来充当SqlSessionFactory。

InitializingBean:

InitializingBean接口中只有一个方法,afterPropertiesSet(),所有实现了该接口的类,在bean的初始化之前都要调用这个方法。可以看出在上面的getObject方法中,如果SqlSessionFactory为空,会调用这个方法创建SqlSessionFactory。

MyBatis 执行流程及源码解析_第3张图片

通过调用SqlSessionFactoryBuilder的build方法,最终返回了一个DefaultSqlSessionFactory实例,这个DefaultSqlSessionFactory中保存了一个非常重要的Configuration对象。

 

二、@MapperScan都做了什么?

在注解配置mybatis时,通过@MapperScan指定Mapper存放的包,就能自动为我们把接口实现成类。那么这是怎么实现的呢?

点开@MapperScan的源码,发现上面还有一行非常重要的注解:

@Import(MapperScannerRegistrar.class)

MyBatis 执行流程及源码解析_第4张图片

ImportBeanDefinitionRegistrar接口提供registerBeanDefinitions方法向用户暴露了BeanDefinitionRegistry,也就是说可以让用户手动创建BeanDefinition并使用该注册器注册到spring容器中。

查看MapperScannerRegistrar的方法registerBeanDefinitions中的核心代码:

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
……
scanner.doScan(StringUtils.toStringArray(basePackages));

主要是创建了一个Mapper扫描器,开启扫描。

ClassPathMapperScanner中doScan方法:

MyBatis 执行流程及源码解析_第5张图片

这里对生成的mapper的bean定义做了进一步处理

进入processBeanDefinitions()方法:

MyBatis 执行流程及源码解析_第6张图片

注意画框代码及上方的注释,先看一下从BeanDefinitionHolder获得BeanDefinition时beanClass初始的值:

MyBatis 执行流程及源码解析_第7张图片

等待setBeanClass执行完毕:

MyBatis 执行流程及源码解析_第8张图片

通过definition.setBeanClass()把原来的BeanClass的类型替换成了MapperFactoryBean类型。到这,完成了Mapper接口加载定义阶段中非常重要的一步,而这也是生成代理对象MapperProxy的关键。

 

三、mybatis如何生成代理对象?

看一下MapperFactoryBean的继承关系:

MyBatis 执行流程及源码解析_第9张图片

MapperFactoryBean继承的SqlSessionDaoSupport类实现了InitializingBean接口,那么我们还是首先找afterPropertiesSet()方法:

MyBatis 执行流程及源码解析_第10张图片

DaoSupport中,最终调用MapperFactoryBean中的方法:

MyBatis 执行流程及源码解析_第11张图片

首先通过获取sqlSession获得了非常重要的配置类Configuration,然后查看一下addMapper方法,最终调用的是MapperRegistry的addMapper方法:

MyBatis 执行流程及源码解析_第12张图片

红框中的代码为我们创建了Mapper 的代理工厂对象(还不是Mapper的代理对象),并把它放入了knownMappers这个Map中。

MyBatis 执行流程及源码解析_第13张图片

在这一步,只是简单初始化了MapperProxyFactory,把我们自己的mapper的类型传给了它,还并没有真正产生代理对象。

MapperRegistry并在之后的parse()方法中完成了xml文件的解析,每一个sql方法都被解析成了一个MappedStatement对象,并添加到了配置类Configuration对象中。

MapperFactoryBean最终返回了什么?

因为MapperFactoryBean实现了FactoryBean接口,所以我们看看getObject方法究竟返回了什么:

MyBatis 执行流程及源码解析_第14张图片

最终调用MapperRegistry的getMapper方法:

MyBatis 执行流程及源码解析_第15张图片

这里调用的了mybatis刚才生成的MapperProxyFactory,帮助我们实例化并返回了一个代理对象。

MapperProxyFactory中使用newInstance方法,实例化MapperProxy,用于生成代理:

MyBatis 执行流程及源码解析_第16张图片

至此,我们已经弄明白了文章开头的MapperProxy是如何生成的。

 

四、MapperProxy代理对象如何执行sql语句?

在StudentServiceImpl中的query方法中打一个断点跟踪语句,你会发现实际执行的就是代理类MapperProxy中的invoke()方法。

MyBatis 执行流程及源码解析_第17张图片

MapperProxy在作为代理类的同时,自身实现了InvocationHandler接口,所以invoke方法就是真正执行的代理逻辑。

MyBatis 执行流程及源码解析_第18张图片

在这里最终调用了MapperMethod的execute方法实际去执行了sql语句。

MyBatis 执行流程及源码解析_第19张图片

 

在该方法中,根据sql语句执行类型,调用sqlSession对应的方法执行并将结果返回给用户。至此,mybatis在spring环境下一次调用全部完成。

 

 

如果您喜欢这篇文章,欢迎关注订阅我的微信公众号 “码农参上”,不定时发布最新文章

公众号后台回复“面试”获取海量大厂面试资料

 

 

 

 

 

你可能感兴趣的:(MyBatis 执行流程及源码解析)