Mybatis源码解析
解析开始准备
我们从一个测试类开始,具体的分析mybatis的执行流程
public class MybatisTest {
public static void main(String[] args) throws IOException {
//1. 读取mybatis-config.xml 文件,包含对与别名,数据库等配置定义
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2. 解析xml文件构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3. 通过SqlSessionFactory,获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4. 获取Mapper类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//5. 获取mapper 接口对象的方法操作数据库
List<User> user = mapper.selectCount();
System.out.println("查询用户数据量为:" + user.size());
}
}
通过SqlSessionFactoryBuilder对xml配置文件进行解析,构建SqlSessionFactory。
- 如果是在整合spring之后,在通过MybatisPlusAutoConfiguration对mybatis进行自动配置时,类上添加了@ConditionalOnClass注解,表明在引入mybatis时需要构建SqlSessionFactory和SqlSessionFactoryBean类
而SqlSessionFactoryBean是一个InitializingBean,在Bean初始化之前会调用他的afterPropertiesSet方法,这里会调用buildSqlSessionFactory构建SqlSessionFactory
解析构建SqlSessionFactory
原生mybatis和spring整合mybatis的两种方法,最终都是通过XMLConfigBuilder对配置文件进行节点的解析,对配置文件中的每一个节点进行解析存入Configuration对象中(以下解析通过原生mybatis来说明)
- 先通过SqlSessionFactoryBuilder.builder()----》XMLConfigBuilder.parse() ----> this.parseConfiguration(this.parser.evalNode(“/configuration”))
- 将plugs解析到configuration的interceptorChain拦截器链中,所以mybatis的插件实际上就是拦截器(代理+责任链)
- 解析别名
- 设置参数类型处理器TypeHandler,默认会注册多个。
- 解析二级缓存,详见二级缓存原理,使用了装饰器模式和责任链模式
- 解析Mappers节点
- 如果是通过mapper package="“的方式或者mapper class=”"的方式配置的,那么就会先将当前包路径下的所有Mapper类,构建成一个MapperProxyFactory代理类,添加到一个map中,我们后续通过sqlsession去拿对应的mapper,去执行指定方法的时候,实际上是拿的这个代理类。之后通过构建一个MapperAnnotationBuilder,加载相同路径下的xml文件【所以刚学mybatis的时候,说xml需要同mapper是相同的路径和名称】,并且通过XMLMapperBuilder解析对应路径下的xml文件中的节点。
- 如果是通过mapper resource="“或者mapper url=”"的方式配置的,那么就通过XMLMapperBuilder解析对应路径下的xml文件中的节点
- 当遇到select|delete|update|update节点的时候,通过XMLStatementBuilder,将各类标签解析成一个个SQLNode,封装成sqlSource,封装成mappedStatement,添加到configuration中专门存放整个statement的map中
- 解析完成构建的configuration对象,设置到SqlsessionFactory中
与Spring整合之后,SqlSessionFactory的构建主要通过自动配置类。比如我们解析Mapper时,添加@Mapper注解或者@MapperScan注解来批量指明具体的Mapper类,在Spring启动的时候会解析这些类【具体怎么解析的后续会出文章解读】,在生成beanDefinition的时候,会将其ClassType设置为MapperFactoryBean,这个类实际上是一个FactoryBean,属于一种特殊的bean,这种类型的bean,通过getObject()方法来创建它所包装的bean。并且MapperFactoryBean还继承了DaoSupport,这是实现的InitializingBean的类,所以MapperFactoryBean在bean初始化(初始化方法前)的时候,会调用其afterPropertiesSet方法。
- afterPropertiesSet方法会给当前的Mapper类生成一个增强的代理工厂类MapperProxyFactory,并且存放到一个map中。当我们真正去获得实例的时候,会从这个map中获取对应的代理工厂类,生成代理对象MapperProxy,放入单例池中。同时会构建一个MapperAnnotationBuilder,使用XMLMapperBuilder解析同路径下的xml文件,解析各个节点,并且添加到configuration中。
- 当我们通过XXXMapper.quey()进行方法调用的时候,实际上获取的是单例池中的代理类
通过Mapper方法查询数据 (原生Mybatis方式的源码)
- 构建完SqlsessionFactory之后,我们通过SqlsessionFactory.opensession()获取到一个sqlsession,这里会按照具体情况委托给对应的执行器处理,如果我们开启了二级缓存,那么将会委托给CachingExecutor,如果未指定那么默认是SIMPLE,继承自BaseExecutor。当然如果我们为执行器添加了插件,那么将会在这里执行【插件执行原理详见下一篇】。
- sqlsession.getMapper(),我们去获取对应的Mapper类,实际上是从configuration的map中获取对应的代理类MapperProxy
- 执行具体的方法的时候,实际上就会执行代理类的invoke方法,判断当前的方法是增删查改中的哪个类型再执行(以select举例)。
- 会从map中获取到对应方法所代表的MappedStatement,然后用指定的执行器执行。
- 解析sql语句,并且将其参数进行预处理,将#和$的符号进行替换
- 对sql语句创建缓存
- 进行具体方法的调用,如果开启了二级缓存,从二级缓存中获取一下,没有的话,就会调用具体执行器的方法去执行(三个执行器的父类就是baseExecutor,这里面实现了一级缓存),从一级缓存中获取一下,没有的话,就会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。可以执行对其增强的插件
- 数据库查询获得结果集,将其放入一级缓存中,然后放入二级缓存。将数据返回
通过Mapper方法查询数据(Spring整合Mybatis之后的源码解析)
在Spring整合Mybtis之后,通过Spring与Mybatis的整合快速入门,我们可以看出,我们并不需要通过SqlsessionFactory.opensession(),然后通过session获取具体的Mapper实例,我们只需要注入Mapper就可以直接调用方法查询。
为什么不需要通过SqlsessionFactory.opensession()就能获取到sqlsession呢?
- 当注入Mapper使用具体方法查询数据库时,实际上我们拿到的是一个Mapper实例实际上是一个MapperFactoryBean的代理类,但是它构造出一个mapper需要借助SqlSession,这里使用的SqlSession其实是SqlSessionTemplate。我们指导MybatisAutoConfiguration会让容器中注入一个SqlSessionTemplate,那么spring是如何把这个SqlSessionTemplate设置到mapperFactoryBean的属性上的昵?
- 实际上是在Bean进行属性赋值的时候,MapperFactoryBean继承了SqlSessionDaoSupport,需要一个sqlSessionTemplate属性,所以在进行属性赋值的时候会从拿到之前注入的实例,通过反射调用set方法注入
- 我们拿MapperFactoryBean实例后,最终时是会调用其getObject()方法,this.getSqlSession().getMapper(this.mapperInterface),最终获得一个MapperProxy的代理对象,当mapper被调用其接口中声明的方法的时候,会调用MapperProxy#invoke
- 而其真正方法的调用又委托给了它的内部类PlainMethodInvoker的invoke方法,最后调用的是MapperMethod#execute,这个方法会根据方法调用的类型(增删改查)调用MapperProxy中的属性SqlSession(根据上面知道sqlSession实现类是SqlSessionTemplate)`对应的方法
- (拿update举例)实际上调用的是sqlsessionTemplate.update()—》this.sqlSessionProxy.update()。这里的sqlSessionProxy是一个代理类new SqlSessionTemplate.SqlSessionInterceptor()。当获取的时候会执行其invoke方法。会根据SqlSessionUtils.getSqlSession拿到当前请求的sqlsession【具体怎么拿到的还有待分析】。