写点什么

MyBatis 执行流程及源码解析

作者:码农参上
  • 2022 年 6 月 25 日
  • 本文字数:2326 字

    阅读完需:约 8 分钟

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


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


public interface StudentMapper {    @Select("select * from student")    public List<Map<String,Object>> query();}
复制代码


在 Servie 中调用 Mapper 的方法:


@Service("studentService")public class StudentServiceImpl implements StudentService {    @Autowired    StudentMapper studentMapper;
@Override public List<Map<String, Object>> query() { return studentMapper.select(); }}
复制代码


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



通过调试,可以知道这时实际的 studentMapper 是一个类型为MapperProxy的代理对象,下面将从 myabtis 环境初始化开始,具体分析代理对象的产生过程。

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

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


@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){   SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();   sqlSessionFactoryBean.setDataSource(dataSource);   return  sqlSessionFactoryBean;}
复制代码


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



先铺垫一下 spring 中两个非常重要的接口,FactoryBeanInitializingBean


FactoryBean:


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



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


InitializingBean:


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



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

二、@MapperScan 都做了什么?

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


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


@Import(MapperScannerRegistrar.class)
复制代码



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


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


ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);……scanner.doScan(StringUtils.toStringArray(basePackages));
复制代码


主要是创建了一个 Mapper 扫描器,开启扫描。看看ClassPathMapperScannerdoScan方法:



这里对生成的 mapper 的 bean 定义做了进一步处理。进入processBeanDefinitions()方法:



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



等待setBeanClass执行完毕:



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

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

看一下MapperFactoryBean的继承关系:



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



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



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



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



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


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


MapperFactoryBean 最终返回了什么?


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




最终调用MapperRegistry的getMapper方法:



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


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



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

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

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



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



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



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

最后

如果觉得对您有所帮助,小伙伴们可以点赞、转发一下,非常感谢

公众号码农参上,加个好友,做个点赞之交啊

发布于: 刚刚阅读数: 3
用户头像

码农参上

关注

公众号:码农参上 2021.03.30 加入

公众号【码农参上】,有趣、深入、与你聊聊技术。

评论

发布
暂无评论
MyBatis 执行流程及源码解析_mybatis_码农参上_InfoQ写作社区