我们在日常工作中广泛使用mybatis作为数据持久层框架,但是mybatis的执行流程是怎么样的,你了解过吗。本文将从源码角度,带你分析mybatis的工作原理。
先看一个简单的例子,以Service调用Mapper接口为,先写一个简单的Mapper:
public interface StudentMapper {@Select("select * from student")public List
在Servie中调用Mapper的方法:
@Service("studentService")
public class StudentServiceImpl implements StudentService {@AutowiredStudentMapper studentMapper;@Overridepublic List
向Service中注入这个Mapper并调用时,你知道这时注入的是什么吗?
通过调试,可以知道这时实际的studentMapper是一个类型为MapperProxy
的代理对象,下面将从myabtis环境初始化开始,具体分析代理对象的产生过程。
一、配置SqlSessionFactoryBean 时都做了什么?
在进行spring和mybatis整合时,会用xml或者注解的方式去配置一个SqlSessionFactoryBean
,本文中以注解方式为例:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;
}
看一下SqlSessionFactoryBean
的继承实现关系:
先铺垫一下spring中两个非常重要的接口,FactoryBean
和InitializingBean
。
FactoryBean:
FactoryBean
是一个spring中比较特殊的Bean,通过它的getObject()
方法可以返回一个对象实例。SqlSessionFactoryBean
中getObject()
方法的实现:
在这里用于创建并返回一个SqlSessionFactory
,在 spring +mybatis 的环境下,我们使用SqlSessionFactoryBean
来充当SqlSessionFactory
。
InitializingBean:
InitializingBean
接口中只有一个方法,afterPropertiesSet()
,所有实现了该接口的类,在bean的初始化之前都要调用这个方法。可以看出在上面的getObject
方法中,如果SqlSessionFactory
为空,会调用这个方法创建SqlSessionFactory
。
通过调用SqlSessionFactoryBuilder
的build
方法,最终返回了一个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扫描器,开启扫描。看看ClassPathMapperScanner
中doScan
方法:
这里对生成的mapper的bean定义做了进一步处理。进入processBeanDefinitions()
方法:
注意画框代码及上方的注释,先看一下从BeanDefinitionHolder
获得BeanDefinition时beanClass
初始的值:
等待setBeanClass
执行完毕:
通过definition.setBeanClass()
把原来的BeanClass的类型替换成了MapperFactoryBean
类型。到这,完成了Mapper接口加载定义阶段中非常重要的一步,而这也是生成代理对象MapperProxy
的关键。
三、mybatis如何生成代理对象?
看一下MapperFactoryBean
的继承关系:
MapperFactoryBean
继承的SqlSessionDaoSupport
类实现了InitializingBean
接口,那么我们还是首先找afterPropertiesSet()
方法:
DaoSupport
中,最终调用MapperFactoryBea
n中的方法:
首先通过获取sqlSession
获得了非常重要的配置类Configuration
,然后查看一下addMapper
方法,最终调用的是MapperRegistry
的addMapper
方法:
红框中的代码为我们创建了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
方法就是真正执行的代理逻辑。
在这里最终调用了MapperMethod
的execute
方法实际去执行了sql语句。
在该方法中,根据sql语句执行类型,调用sqlSession
对应的方法执行并将结果返回给用户。至此,mybatis在spring环境下一次调用全部完成。
作者:Hydra
链接:https://juejin.cn/post/7018360428178702366
来源:稀土掘金