MyBatis 的源码相对于 Spring 来说很简单。通过各种 Builder 构造器解析配置文件,并完成 SqlSessionFactory
的创建。
整体的 Spring 流程就是:
MapperProxy
去动态生成 SQL(由 Executor 完成执行)。XMLConfigBuilder
XMLConfigBuilder 用于构建 Configuration
对象。包含了 MyBatis 需要的所有的信息,并会根据该 Configuration
对象创建 SqlSessionFactory
工厂,用于创建 Session、Mapper 对象。
在 XMLConfigBuilder 中会解析到 mybatis-config.xml
文件中的配置项,例如:environments
、mappers
、settings
、plugins
等等配置。
XMLConfigBuilder 对象调用 parse 方法,进入 parseConfiguration 方法,开始逐步解析 XML 配置文件并完成相关配置的解析创建 Configuration
对象。
/**
* 根据 XML 文件中的配置信息,逐步解析创建 Configuration 对象
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
// 在这里解析插件
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 在这里根据配置的信息创建:TransactionFactory、DataSourceFactory、DataSource
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 在这里向下深入,将进行 Mapper 文件的扫描解析、创建
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLMapperBuilder
XMLMapperBuilder 用于分析 Mapper,他的作用主要是通过解析 Mybatis 的 Mapper 文件来完成 Mapper 对象的创建和初始化工作。
Mapper 的配置一般是有 package、resource、url、class 这四种。
在 XMLMapperBuilder 中会解析到 Mapper 的 xml 文件中的所有标签/节点。我们常用的有:cache
、resultMap
、SQL 相关的标签(select
、update
)。
/**
* 解析 Mapper xml 文件
*/
private void configurationElement(XNode context) {
try {
// 指定 namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 缓存标签
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// resultMap 标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// select 标签
sqlElement(context.evalNodes("/mapper/sql"));
// SQL 相关的标签(select、insert、update、delete),在这里向下深入,将解析生成 MappedStatement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
XMLStatementBuilder
XMLStatementBuilder
将解析 SQL 相关的标签(select、insert、update、delete)生成 MappedStatement
。
获取 XMLLanguageDriver
并由 XMLScriptBuilder
完成 SQL 标签的解析,最后创建成 MappedStatement
记录到 configuration
中。
/**
* 解析 SQL 相关的标签(select、insert、update、delete)
*/
public void parseStatementNode() {
....
// 获取 XMLLanguageDriver,由 XMLScriptBuilder 解析成 SqlSource
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
....
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
....
// 创建 MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
XMLScriptBuilder(XMLLanguageDriver)
XMLScriptBuilder 用于解析 SQL 标签( where
、if
等)生成 SqlSource
。
/**
* 解析 SQL 标签( `where`、`if` 等)生成 SqlSource
*/
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
在解析 Mapper 文件时,根据是否存在 cache
标签创建对应的二级缓存。
private void configurationElement(XNode context) {
....
// 解析创建二级缓存的入口
cacheElement(context.evalNode("cache"));
....
}
二级缓存采用的装饰者模式,不同的装饰者承担了不同的责任,例如:日志、同步、序列化。
最核心的缓存实现是:org.apache.ibatis.cache.impl.PerpetualCache
。
生成的缓存对象的委托结构如下:
- org.apache.ibatis.cache.decorators.SynchronizedCache (缓存的同步)
- org.apache.ibatis.cache.decorators.LoggingCache (缓存的日志输出)
- org.apache.ibatis.cache.decorators.SerializedCache (缓存序列化)
- org.apache.ibatis.cache.decorators.LruCache (缓存的失效策略)
- org.apache.ibatis.cache.impl.PerpetualCache (缓存数据存储)
在执行就会从 SynchronizedCache --> LoggingCache 的方向逐步调用,经过装饰者的处理,完成不同的功能。
插件会在 XMLConfigBuilder 解析最外层配置文件时生成对应的插件拦截器,并添加到 interceptorChain
执行链中。
在调用的 MyBatis 的四大对象时(Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
)会到 interceptorChain
执行链中匹配合适的插件(匹配规则:指定的四大对象,然后根据方法名称和签名完成匹配)。
举个例子来理解插件如何生效的。
Executor 在 DefaultSqlSessionFactory#openSessionFromDataSource
开启 Session 时完成创建 Executor 并使用动态代理织入插件。
/**
* 创建 Executor
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 插件将通过动态代理应用到 Executor 对象上:Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
return (Executor) interceptorChain.pluginAll(executor);
}
在 Executor 执行时创建会创建 StatementHandler
(默认 StatementType.PREPARED
创建 PreparedStatementHandler
)。
StatementHandler 对象还需要同时创建两个对象:parameterHandler
、resultSetHandler
。
四大对象时(Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
)都会通过 configuration
完成插件的代理。
// ParameterHandler 插件代理的创建
return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
// ResultSetHandler 插件代理的创建
return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
// StatementHandler 插件代理的创建
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
// Executor 插件代理的创建
return (Executor) interceptorChain.pluginAll(executor);
Spring 和 MyBatis 的集成依赖于项目:https://github.com/mybatis/spring。
MyBatis 通过自定义 Class 扫描器,来完成 Mapper 接口的扫描,并修改扫描出来的 BeanDefinition
。
将 Bean 定义中的 Class 改为 MapperFactoryBean
,通过 FactoryBean 的 getObject()
调用sqlSession 创建 Mapper 代理对象。
MyBatis 关键类说明
@Import(MapperScannerRegistrar.class)
导入 MapperScannerRegistrarMapperScannerConfigurer
BeanDefinitionRegistryPostProcessor
)ClassPathMapperScanner
Class 扫描器,完成 Mapper 接口的扫描。ClassPathBeanDefinitionScanner#doScan
并重写 isCandidateComponent
、checkCandidate
实现扫描 Mapper 接口成 Bean 定义。processBeanDefinitions
将扫描出来的接口 Bean 定义中的 Mapper.class
改为 MapperFactoryBean.class
,并将接口的 Class 通过参数传递给 MapperFactoryBean。MapperFactoryBean#getObject
。getSqlSession().getMapper(this.mapperInterface)
完成实际的 Mapper 代理创建。修改了 Mapper 接口 Bean 定义的 Class,让 Bean 定义转变成了一个工厂 Bean。
所以该 Bean 定义转变成 Bean 的实例化过程就从普通的 Spring 实例化,变成了 MapperFactoryBean#getObject
创建。
通过工厂 Bean 实例化 Mapper,就可以由 MapperFactoryBean
自定义实例化 Mapper 接口(动态代理)。
依赖
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation("mysql:mysql-connector-java:8.0.32")
implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.1.0'
implementation("org.mybatis:mybatis:3.5.13")
implementation(project(":spring-context"))
implementation(project(":spring-jdbc"))
implementation(project(":spring-aop"))
implementation(project(":spring-jdbc"))
implementation("com.alibaba:druid:1.1.8")
}
配置类
@Configuration
//扫描指定包中的mapper文件
@MapperScan("com.devnolo.demo.spring.mybatis.mapper")
@ComponentScan("com.devnolo.demo.spring.mybatis")
//提供事务支持
@EnableTransactionManagement
public class ApplicationConfiguration {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://192.168.0.0:3306/test?characterEncoding=utf8");
dataSource.setUsername("user");
dataSource.setPassword("password");
dataSource.setMaxActive(100);
dataSource.setMinIdle(20);
dataSource.setMaxWait(1000);
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource());
// 设置 SQL 映射文件路径
factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
return factory;
}
@Bean
public TransactionManager transactionManager(){
DataSourceTransactionManager trans = new DataSourceTransactionManager();
//设置数据源
trans.setDataSource(dataSource());
return trans;
}
}
启动类
public class DemoMyBatisApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
TestMapper bean = applicationContext.getBean(TestMapper.class);
Test test = bean.selectOne(1L);
System.out.println(Optional.ofNullable(test).map(Test::getId).orElse(null));
}
}
Mapper
Mapper 接口
public interface TestMapper {
Test selectOne(Long id);
}
实体
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Mapper XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.devnolo.demo.spring.mybatis.mapper.TestMapper">
<cache/>
<select id="selectOne" resultType="com.devnolo.demo.spring.mybatis.model.Test">
select * from test
<where>
<if test="id > 0">
and id = #{id}
</if>
</where>
</select>
</mapper>