目录
- 整体架构与使用Demo
- Mybatis核心流程
-
- 1. 创建SqlSessionFactory
-
- 配置解析——SQL解析(存储MappedStatement到Configuration中)
- SqlNode
- MappedStatement
- SqlSource
- BoundSql
- 2. 创建SqlSession
-
- 3. 获取Mapper
- 4. 通过Mapper接口调用CRUD方法
-
- 5. Executor执行SQL
-
整体架构与使用Demo
Mybatis的架构整体可以分为3层:
- 接口层
也就是和数据库进行交互,核心接口为SqlSession,一个SqlSession对应着一次数据库会话,那么其生命周期不是永久的,理论上每次访问数据库时都需要创建它。
形式分为两种,一种是使用Mapper接口,一种是基于Mybatis提供的Api;
- Mybatis提供的Api
需要我们提供StatementId和查询参数,传递给SqlSession对象,提供SqlSession对象实现与数据库的交互;但是这种创建sqlSession的形式不符合面向接口编程的习惯。
- 使用Mapper接口:
Mybatis将配置文件中的每一个Mapper节点都抽象为一个Mapper接口,根据SqlSession.getMapper(XXXMapper.class),Mybatis将通过动态代理,生成一个Mapper实例。但当我们调用Mapper接口中的方法时,Mybatis会根据方法名和参数,确定StatementId,底层还是通过SqlSession来实现对数据库的操作。
- 数据处理层
- 配置解析
- 参数、结果集映射:java数据类型与jdbc数据类型的转换,包括查询阶段和结果返回阶段
- Sql解析:动态sql生成
- Sql执行
- 框架支持层
使用Demo:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List list = sqlSession.selectList("com.example.mapper.personMapper.selectPersonByMap");
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
personMapper.selectPersonByMap();
Mybatis的初始化有两种形式:
- 基于XML配置文件:通过XML配置文件,将配置信息解析为Configuration对象。
- 基于Java API:在Java代码中手动创建Configuration对象,然后将配置参数set 进入Configuration对象中。(不推荐)
Mybatis核心流程
1. 创建SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
将xml配置信息解析为Configuration对象,然后构建一个DefaultSqlSessionFactory类型的工厂:
- 创建XMLConfigBuilder,解析xml全局文件
- 创建XMLMapperBuilder,解析每个xml
- 最后将返回Configuration对象,保存着全部的xml配置信息;
配置解析——SQL解析(存储MappedStatement到Configuration中)
我们重点分析XMLMapperBuilder.parse方法中,对sql相关信息的解析过程:
- 解析Mapper节点,获得XNode,再解析XNode中的 select|insert|update|delete 节点,保存为XNode类型的List;遍历CRUD的XNode节点:
- 新建XMLStatementBuilder,调用parseStatementNode,用于解析每一个CRUD的节点
- 创建LanguageDriver(默认为XMLLanguageDriver,用于处理xml中的sql部分)
- 调用LanguageDriver.createSqlSource,获取SqlSource
- 根据SqlSource创建MappedStatement,并且注册到Configuration中
createSqlSource获取SqlSource的方法:
- 新建XMLScriptBuilder,并调用parse解析各个节点的sql部分
- 递归解析,最终得到MixedSqlNode(本质为SqlNode的List集合),并且标记此Sql类型为动态还是静态
- 如果是动态类型,返回DynamicSqlSource
- 如果是静态类型,返回RawSqlSource
- 返回的SqlSource将会保存在MappedStatement中,然后等sql执行的时候,通过getBoundSql方法触发sql的解析
总结
- XMLConfigBuilder
解析xml整体文件
- XMLMapperBuilder
解析每个xml文件
- XMLStatementBuilder
解析xml文件中各个select,insert,update,delete节点
- XMLScriptBuilder
解析xml中各个节点sql部分的Builder,产生MapperStatement,保存到Configuration中
SqlNode
节点解析时的工具类,简单理解就是xml中的每个标签,如update,trim,if标签
其实现类包括
实现动态Sql的关键就是 各个SqlNode的 apply方法
以IfSqlNode为例,如果满足条件,则apply,并返回true
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
而StaticTextSqlNode类型静态sql,则直接append
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
MappedStatement
是Configuration中的属性,表示一个CRUD节点的信息
SqlSource
是MappedStatement的属性,实现类包括:
- StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
- RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,比动态SQL语句要快,因为不需要运行时解析SQL节点。
- DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理if等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
- ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。
其getBoundSql方法提供BoundSql对象。
BoundSql
是从SqlSource中获取得到的信息,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数;后续在执行Sql的时候将会使用到BoundSql。
2. 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
- 创建Transaction事务:
- 根据Configuration配置信息获取Environment环境对象
- 根据environment信息获取TransactionFactory,这里获取到的事务工厂,即对应着xml文件中的transactionManager节点
- newTransaction创建事务tx
- 创建Executor执行器:
- 调用Configuration对象的newExecutor方法,将事务对象tx传参进去;
- newExecutor方法,默认创建的是Simple类型的SimpleExecutor
(类型一共三种:SIMPLE, REUSE, BATCH)
如果开启了cacheEnabled(默认开启),那么包装为CachingExecutor:在查询数据之前先查找缓存,没有找到再从数据库查询并加入缓存。
- executor = (Executor) interceptorChain.pluginAll(executor);
使用了执行器链模式,使得Executor对象可以被插件拦截;
- 创建DefaultSqlSession:
- new DefaultSqlSession(configuration, executor, autoCommit)
SqlSession
Mybatis与数据库交互的核心接口,一个SqlSession对应着一次数据库会话。
Executor
执行器,真正的sql执行并不是SqlSession直接执行的,而是通过Executor去执行。
3. 获取Mapper
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
- 通过MapperRegister(Map类型的Mapper注册器Map, MapperProxyFactory>>)根据接口Class信息,获取到目标MapperProxyFactory
- 通过MapperProxyFactory创建MapperProxy(也就是Mapper接口的代理)
- Proxy.newProxyInstance动态代理创建
- 至此,我们通过SqlSession.getMapper获取到的personMapper对象,实际上是一个代理类
4. 通过Mapper接口调用CRUD方法
personMapper.selectPersonByMap();
被代理对象的方法的访问,都会落实到代理者的invoke上来,所以调用的mapper.select等方法,将会经过MapperProxy中的invoke:
- 创建MapperMethod对象:
根据CRUD的类型将调用分发到SqlSession的不同方法上
- 处理参数:
convertArgsToSqlCommandParam:当args数量>1时,将Object[]的参数包装为Map
- SqlSession方法的执行:
- 根据MapperMethod内部类SqlCommand中存储的statementId,从Configuration中找对应的MappedStatement
- 将MappedStatement作为参数,调用Executor中对应的方法
MapperMethod
它是一个分发者,根据CRUD的类型将调用分发到SqlSession的不同方法上。
有两个内部类同时被创建:
- SqlCommand
interfaceName + methodName作为statementId,去Configuration中去找对应的MappedStatement
存储statementId、SqlCommandType(CRUD类型)信息
- MethodSignature
记录方法的参数、返回值等信息
5. Executor执行SQL
- 我们默认为调用的是有缓存的Executor,那么先本地查询缓存,如果没找到再去调用(默认简单类型)SimpleExecutor的query方法
- 创建StatementHandler:
默认类型为ParparedStatementHandler,Executor将执行的任务交给它,它才是sql的具体执行者。也可以被插件所拦截(常见的物理分页插件)
- prepare:预编译sql,得到Statement对象
- 调用ParameterHandler:处理参数
- 调用ResultSetHandler:处理执行结果
StatementHandler
由此可见,Executor也不是真正的执行者,而是由StatementHandler完成的。在StatementHandler被创建的过程中,有一个getBoundSql的步骤,触发了动态sql的处理,下节详细分析。