我这里分析的源码是spring-mybatis整合的源码
先扒一张mybatis架构图镇楼
一:初始化
mybatis-spring-boot-autoconfigure org\mybatis\spring\boot\autoconfigure\MybatisAutoConfiguration.class mybatis的一切都起源于这里
初始化的时候MybatisPropertise中封装着我们在配置文件中加的关于mybatis所有配置
如果使用了MapperScan注解的话,可以看到该注解上面有import注解,会执行import中指定类的方法,从而完成包扫描,这里不详细分析了
没有MapperScan注解,会走这里,通过Import注解注入AutoConfiguredMapperScannerRegistrar类
在AutoConfiguredMapperScannerRegistrar类中,会生成一个扫描器,获取包路径,规定扫描Mapper注解,并开始扫描包路径下的所有mapper注解
获取到所有的Mapper注解的类后,对他们进行处理,为这些接口指定一个具体的实现:MapperFactoryBean,并设置它们注入的时候按照类型进行注入
之后会生成所有接口的实现类对象,因为设置具体实现都是MapperFactoryBean,所以会调用MapperFactoryBean的有参构造,将对应的接口传入,生成针对每个接口不同的MapperFactoryBean对象
在注入第一个dao实现类的时候,发现还需要SqlSessionFactory,SqlSessionTemplate这两个对象,容器中没有,这时候会找到MybatisAutoConfiguration 这个类,没错,这俩对象都在这个类中
然后初始化 sqlsessionfactory (先根据配置生成 sqlsessionfactorybean 然后调用它的getObject生成单例的sqlsessionfactory 对象)
getObject中会生成一个configuration对象,将该对象组装完成后,调用sqlSessionFactoryBuilder.build(configuration)构建出sqlSessionFactory对象
构建configuration对象时做了许多事情,比如:
第一次配置configuration对象时,只改变了vfsimpl,environment了两个对象,别的属性都没变化
使用的是spring的事务管理对象,dataSource中是关于数据源的所有配置,这两个对象也被配置到了configuration中
加载了所有mybatis的xml文件,分析这些文件,并将dao和xml绑定起来
所有xml和dao中的信息都缓存在Configuration对象中,这个对象是单例的,而且几乎是到处传,几乎所有的mybatis重要的对象中都有这个对象,整个创建工厂的过程,说白了就是完善了Configuration对象,然后把Configuration对象封装到了DefaultSqlSessionFactory中
下一步,创建sqlsessiontemplate,并注入spring容器中
在生成该对象的时候,会在sqlsessiontemplate中用动态代理生成一个新的sqlsession对象,这里面封装了事务方面的东西
二:mapper动态代理对象的注入
MapperFactoryBean中有个属性叫SqlSession,放的其实就是SqlSessionTemplate对象,之后调用MapperFactoryBean的getObject方法,层层调用后会从Configuration中获取到对应dao的MapperProxyFactory工厂,由这个工厂生成对应dao的动态代理实现类
三: 执行mapper的具体方法
动态代理的方法,前面两个方法我也不知道是干啥的,正常情况下只会执行第三个方法
在这里会创建MapperMethod对象,构造方法中,第一个方法会获取sql的类型(增删改查),
第二个方法会获取这个方法的入参类型和返回值类型
开始执行sql,根据sql的类型和返回值走不同的方法
我这里走的是入参只有一个(String),返回值为一个对象
查询步骤:
1:将参数名和参数值映射起来
执行完毕后,param中每个入参会建立两个键值对,一个将参数名和参数值映射起来,另一个将参数的位置和参数值映射起来
查询的时候是调用SqlSessionTemplate中的另一个对象SqlSession,是个动态代理的对象
这里又是一片新天地…
第一行就是获取sqlsession对象的方法,然后会执行对应的方法,最后提交事务
有事务注解的话会尝试从事务管理器中取session,拿不到sqlsession对象的话,就从工厂中获取sqlsession对象,最后,有事务注解的话把这个sqlsession注册到事务管理器中
工厂的openSession方法跟下去会进入到这里,这里创建了事务,执行器对象,最终执行sql的就是执行器
这是构建执行器里面的方法,会根据执行类型类型创建对应的执行器,我们这里创建的是简单执行器,然后,需要缓存,会在简单执行器外面包一层,生成一个带缓存的执行器
然后是最后一行,会对所有的插件进行处理,如果有分页助手等插件的话,会在当前的执行器外面再包一层,生成一个带插件的功能的动态代理的执行器
一个执行器被这么层层包裹,心累…
最后会生成一个DefaultSqlSession对象
所以,看起来是SqlSessionTemplate中的SqlSession对象执行了selectOne等方法,其实是在它里面生成了另一个sqlSession,用那个新的sqlSession执行的对应的方法
调query方法时有坑,感觉一直不执行query方法,然后结果就返回来了,这感觉是idea的bug,因为有分页助手等插件在这儿拦截,可能引起了idea的错误判断,其实是会执行这儿的
这里,MappedStatement对象是在全局唯一的Configuration对象中放着的,这个就是传说中的二级缓存,全局的缓存,需要设置才会生效
这里的localCache就是传说中的一级缓存,也就是那个sqlSession级别的缓存,缓存中有的话,直接从缓存中取值,没有的话从数据库查,不过感觉这个一级缓存唯一会起作用的地方就是加事务的时候,只有事务开启的时候,所有查询才会获取到同一个sqlSession,其他情况都是一条sql一个sqlSession,基本上不会出现多条sql用同一个sqlsession的情况
设置sql的各种初始化参数,在这个方法里进行的sql预编译
在这里将查询结果取出来,并获取字段名,数据库中的字段类型等所有与结果有关的信息
这里,将查询结果封装成返回的对象
至此,整个流程跑通,最后,用流程图串一下整个流程