前言
mybatis中文官网地址:https://mybatis.org/mybatis-3/zh/index.html
一、Mybatis介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
后面所有的代码都是Mybatis的源码!!!!!java代码!!!!!
二、Mybatis配置文件
截图自中文官网:
配置文件样例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--详细参考官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties-->
<!--配置文件的路径-->
<properties resource="mybaits.properties"></properties>
<!--设置(settings)-->
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
<!--类型别名(typeAliases)-->
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
<!--类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型
-->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
<!--对象工厂(objectFactory)-->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
<!--插件(plugins)-->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
<!--环境配置(environments)-->
<environments default="development">
<environment id="development">
<!--事务管理器(transactionManager)-->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--数据库厂商标识(databaseIdProvider)-->
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<!--映射器(mappers)-->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!--
使用完全限定资源定位符(URL)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
-->
</configuration>
三、SqlSessionFactory源码解析
根据工具类创建SqlSessionFactory对象
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
进入build方法
// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 把传入的Mybatis的配置文件路径,包装成一个XMLConfigBuilder对象,解析xml
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// build方法返回一个DefaultSqlSessionFactory对象
// parse方法用来解析xml配置文件,返回Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
进入XPathParser的parse方法
// 当前类:XPathParser
public Configuration parse() {
// 默认时false
if (parsed) {
// 全局配置文件不可以重复解析
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 改为true
parsed = true;
// 解析入口,parser.evalNode("/configuration")返回XNode对象,xml文件解析成的java bean
parseConfiguration(parser.evalNode("/configuration"));
// 返回全局配置文件
return configuration;
}
进入parseConfiguration方法
// 当前类:XMLConfigBuilder
private void parseConfiguration(XNode root) {
try {
//解析全局配置文件的<configuration/>标签下的<properties/>标签
//保存在Configuration的variables属性和XPathParser的variables属性里
propertiesElement(root.evalNode("properties"));
//解析全局配置文件的<configuration/>标签下的<setting/>标签到Properties对象里面
Properties settings = settingsAsProperties(root.evalNode("settings"));
//解析全局配置文件的<configuration/>标签下的<vfsImpl/>标签,从本地和FTP加载文件
loadCustomVfs(settings);
//解析全局配置文件的<configuration/>标签下的<logImpl/>标签,LOG4J,LOG4J2,SLF4J
loadCustomLogImpl(settings);
//解析全局配置文件的<configuration/>标签下的<typeAliases/>标签,类别名
typeAliasesElement(root.evalNode("typeAliases"));
//解析全局配置文件的<configuration/>标签下的<plugins/>标签,所有的插件类
pluginElement(root.evalNode("plugins"));
//解析全局配置文件的<configuration/>标签下的<objectFactory/>标签,对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//解析全局配置文件的<configuration/>标签下的<objectWrapperFactory/>标签,对象装饰类
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析全局配置文件的<configuration/>标签下的<reflectorFactory/>标签,辅助性工厂类
reflectorFactoryElement(root.evalNode("reflectorFactory"));
///给全局配置类configuration设置属性,没有则用默认值
settingsElement(settings);
//解析全局配置文件的<configuration/>标签下的<environments/>标签,多个数据源
environmentsElement(root.evalNode("environments"));
//解析全局配置文件的<configuration/>标签下的<databaseIdProvider/>标签,数据源标识
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析全局配置文件的<configuration/>标签下的<typeHandlers/>标签,jdbcType和javaType映射类
typeHandlerElement(root.evalNode("typeHandlers"));
//解析全局配置文件的<configuration/>标签下的<mappers/>标签,sql所在文件,
//支持xml文件路径(resource,url,文件下的sql标签如<select>),
//class文件(package,class,这种格式的去解析方法上的Mybatis注解如:@Selcet),
//最后将他们解析为MappedStatement放在全局配置类configuration的mappedStatements里面
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
进入mapperElement(root.evalNode("mappers"))方法,讲解所有sql标签生成MappedStatement的过程
// 当前类:XMLConfigBuilder,parent为mapper标签
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 映射器<mapper>支持四种方式,<!-- 使用相对于类路径的资源引用 -->
// <!-- 使用完全限定资源定位符(URL) --> <!-- 使用映射器接口实现类的完全限定类名 -->
// <!-- 将包内的映射器接口实现全部注册为映射器 -->
// 最常用的就是xml,第一种<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 把xml文件包装为流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 用解析工具类进行解析,resource="org/mybatis/builder/AuthorMapper.xml"
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析入口
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
进入XMLMapperBuilder的parse方法
// 当前类:XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper的xml文件的mapper标签
configurationElement(parser.evalNode("/mapper"));
// 保存resource到Configuration对象loadedResources属性里面,
// resource="org/mybatis/builder/AuthorMapper.xml"
configuration.addLoadedResource(resource);
// 把所有的namespace属性值保存到Configuration对象mapperRegistry属性里面
// <mapper namespace="test.Mapper">
bindMapperForNamespace();
}
// 这几个用于解析配置文件发生异常之后,再次处理,后面会看到
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
进入bindMapperForNamespace方法,这个方法要注意,很重要
// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 这里要注意,在生成Mapper对象时要用到
configuration.addMapper(boundType);
}
}
}
}
// 当前类:org.apache.ibatis.session.Configuration
public <T> void addMapper(Class<T> type) {
// 进入这里
mapperRegistry.addMapper(type);
}
// 当前类:org.apache.ibatis.binding.MapperRegistry
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 在mybaits中,namespace属性值就是接口的全路径,所有在这里保存knownMappers集合中
// 以接口class对象为key,MapperProxyFactory对象为value保存在knownMappers集合中,
// 一个class对应一个MapperProxyFactory对象
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
进入configurationElement方法
// 当前类:XMLMapperBuilder
private void configurationElement(XNode context) {
try {
//得到namespace属性值,假如为:test.Mapper
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置namespace属性,以后用namespace+id来保存MappedStatement对象
builderAssistant.setCurrentNamespace(namespace);
//解析mapper标签下的<cache-ref/>标签,引入其他命名空间二级缓存
cacheRefElement(context.evalNode("cache-ref"));
//解析mapper标签下的<cache/>标签,用来设置二级缓存,没有该标签不使用二级缓存
cacheElement(context.evalNode("cache"));
//解析mapper标签下的<parameterMap/>标签,不常用
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析mapper标签下的<resultMap/>标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析mapper标签下的<sql/>标签,重复sql提取
sqlElement(context.evalNodes("/mapper/sql"));
//解析mapper标签下的增删改查标签,讲解这个
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);
}
}
进入buildStatementFromContext方法
// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list) {
// 获取设置的数据库标识
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId())
}
// 没有设置为null
buildStatementFromContext(list, null);
}
进入buildStatementFromContext方法
// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 解析工具类
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 解析发生异常,在org.apache.ibatis.builder.xml.XMLMapperBuilder#parse方法继续处理
// 可以看之前的XMLMapperBuilder的parse方法解析
configuration.addIncompleteStatement(statementParser);
}
}
}
进入parseStatementNode方法
// 当前类:org.apache.ibatis.builder.xml.XMLStatementBuilder
public void parseStatementNode() {
// 得到sql的id
String id = context.getStringAttribute("id");
// 数据库标识
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 下面这些就是获取sql标签属性信息
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENG
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builder
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandTyp
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterType
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statem
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 利用这个方法将xml的sql数据转换为MappedStatement对象,参数是sql的一些基本信息
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
进入addMappedStatement方法
// 当前类:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 利用构建者模式,构建MappedStatement对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 返回一个MappedStatement对象,完成xml的sql转换为MappedStatement的过程
MappedStatement statement = statementBuilder.build();
// 保存在Configuration配置类的mappedStatements属性里面
configuration.addMappedStatement(statement);
return statement;
}
到这SqlSessionFactory的源码就解析完了,主要就是把xml的配置信息进行java对象化,生成一个全局的配置对象Configuration,然后保存在DefaultSqlSessionFactory对象里面,然后返回就完了。
四、SqlSession源码解析
上面介绍了SqlSessionFactory的源码,那么接下来就是根据SqlSessionFactory得到SqlSession对象了。
SqlSession session = sqlSessionFactory.openSession();
进入openSession方法
// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public SqlSession openSession() {
// 参数:默认的执行器,事务管理器,是否自动提交
// 具体的执行器这些参数,可以查看那个中文文档进行了解
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
// 事务管理器对象
Transaction tx = null;
try {
// 得到环境变量对象
final Environment environment = configuration.getEnvironment();
// 事务管理器工厂对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建一个事务管理器
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 返回一个DefaultSqlSession,包含全局配置文件,执行器,和是否自动提交标志
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
进入newExecutor方法
// 当前类:org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据配置的执行器类型,得到一个执行器对象,执行器的知识参考mybaits中文文档
if (ExecutorType.BATCH == executorType) {
//批量执行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//复用Statement对象的执行器
executor = new ReuseExecutor(this, transaction);
} else {
//简单执行器
executor = new SimpleExecutor(this, transaction);
}
// 如果cacheEnabled属性为true,对执行器进行包装,cacheEnabled看中文官网的settings属性介绍
if (cacheEnabled) {
// 装饰器模式,带有缓存功能的执行器
executor = new CachingExecutor(executor);
}
// 对执行器进行插件处理,就是对它功能进行增强,具体看中文官网的plugins属性介绍
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
进入pluginAll方法,这里对插件的功能介绍之后,在后面对ParameterHandler,ResultSetHandler和StatementHandler 对象进行插件处理时,就不多介绍了,都是一样的逻辑。
// 当前类:org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {
// interceptors就是实现插件增强的全部实现类的集合,具体在中文官网看plugins插件配置的介绍
for (Interceptor interceptor : interceptors) {
// 采用责任链模式进行,层层代理,使得执行器的功能更多
target = interceptor.plugin(target);
}
// 返回代理的执行器对象,赋值给DefaultSqlSession对象
return target;
}
插件实现的样例,对官网提供的代码进行改造
package test;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
import java.util.concurrent.Executor;
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
System.out.println("我是执行器,我要开始工作了。。。。。。");
Object returnObject = invocation.proceed();
// implement post processing if need
System.out.println("我是执行器,我工作结束了,下班。。。。。。");
return returnObject;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
进入plugin方法,假设目前只有上面这一个执行器的插件
// 当前类:test.ExamplePlugin,就是上面的那个样例类
public Object plugin(Object target) {
// 进入wrap方法,target执行器对象:CachingExecutor,this插件对象:ExamplePlugin
return Plugin.wrap(target, this);
}
进入wrap方法
// 当前类:org.apache.ibatis.plugin.Plugin
// 这个里面的getSignatureMap和getAllInterfaces方法可以多看看很有意思
public static Object wrap(Object target, Interceptor interceptor) {
// 得到当前插件拦截器的注解信息,取到它拦截的什么类和拦截的什么方法,注解格式
// @Intercepts({@Signature(type = Executor.class,method = "query",
// args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 得到当前对象和其父类,满足插件拦截的所有类
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果当前对象满足当前插件的拦截
if (interfaces.length > 0) {
// 返回一个JDK代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 如果当前对象不满足,当前插件的拦截条件就返回,让下一个插件继续处理
return target;
}
JDK代理的对象,在对象执行方法时,会进入实现了InvocationHandler接口的invoke方法,这是JDK的知识,可以自己去学习。进入Plugin的invoke方法。
// 当前类:org.apache.ibatis.plugin.Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 得到当前执行方法的类对象,查询该对象满足的所有拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 包括当前执行的方法
if (methods != null && methods.contains(method)) {
// 就执行插件的intercept方法,把当前对象,方法和参数传入,intercept假设为ExamplePlugin
return interceptor.intercept(new Invocation(target, method, args));
}
// 没有或者不包括当前执行的方法,就直接执行该方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
进入ExamplePlugin的intercept方法
package test;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
import java.util.concurrent.Executor;
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
// 这个就是我们自定义的插件实现类
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
System.out.println("我是执行器,我要开始工作了。。。。。。");
// 等插件增强后,执行目标方法,Invocation的代码在下面
Object returnObject = invocation.proceed();
// implement post processing if need
System.out.println("我是执行器,我工作结束了,下班。。。。。。");
return returnObject;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author Clinton Begin
*/
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
// 就是执行原来的方法本身
return method.invoke(target, args);
}
}
插件的实现就是JDK动态代理,我认为Mybatis是最开始学习源码的最佳之选,因为它通俗易懂。
五、Mapper源码解析
得到SqlSession对象之后,我们就可以得到Mapper对象了,因为Mapper是一个接口,所有肯定是一个代理对象,不知道JDK代理的原理是看不懂的。
Mapper mapper = session.getMapper(Mapper.class);
int i = mapper.selectCount("1");
进入getMapper方法
// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public <T> T getMapper(Class<T> type) {
// configuration全局配置对象,就是在生成DefaultSqlSession对象,通过构造方法传入的
return configuration.getMapper(type, this);
}
// 当前类:org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据mapperRegistry得到代理对象
return mapperRegistry.getMapper(type, sqlSession);
}
进入getMapper方法
// 当前类:org.apache.ibatis.binding.MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 看之前SqlSessionFactory源码解析的bindMapperForNamespace方法
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 生成代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
进入newInstance方法
// 当前类:org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 生成的代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 生成一个MapperProxy对象,包含SqlSession,Mapper接口对象,ConcurrentHashMap
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// this的newInstance方法
return newInstance(mapperProxy);
}
}
既然用JDK代理,我门就要进入MapperProxy的invoke方法了
// 当前类:org.apache.ibatis.binding.MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object的方法直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// JDK1.8的新特性 接口中可以定义默认方法
} else if (isDefaultMethod(method)) {
// 这里没有深入研究过
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 这个很有意思
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
进入cachedMapperMethod
// 当前类:org.apache.ibatis.binding.MapperProxy
private MapperMethod cachedMapperMethod(Method method) {
// methodCache是一个Map<Method, MapperMethod>集合
// 把当前method对象,和包装method的MapperMethod对象保存起来
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
// 当前类:org.apache.ibatis.binding.MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 这个方法是根据传入的参数,从MappedStatements中获取一个MappedStatement对象,
// 包含了sql语句的主要信息,包装为SqlCommand对象
this.command = new SqlCommand(config, mapperInterface, method);
// 这个就是将传入的信息,封装在一个内部类里面,Mapper接口和全局配置文件信息
this.method = new MethodSignature(config, mapperInterface, method);
}
// 当前类:org.apache.ibatis.binding.MapperMethod
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// ms对象,可以看DefaultSqlSessionFactory源码解析
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
进入mapperMethod.execute(sqlSession, args)方法
// 当前类:org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据不同的sql类型进行处理
switch (command.getType()) {
case INSERT: {
// 把参数进行处理
Object param = method.convertArgsToSqlCommandParam(args);
// result为sql执行的结果,进入insert方法,command.getName()为id,
// id=namespace+id(sql标签的id),用来从mappedStatements取ms对象
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
进入insert方法
// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public int insert(String statement, Object parameter) {
// statement为id,parameter为参数
return update(statement, parameter);
}
进入update方法
// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public int update(String statement, Object parameter) {
try {
dirty = true;
// 取ms对象,很简单的代码,不看了
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器executor,一般为CachingExecutor而且被插件增强的执行器代理对象
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
进入执行器的update方法
// 当前类:org.apache.ibatis.executor.CachingExecutor
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 修改操作,要删除二级缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
进入delegate.update(ms, parameterObject)方法
// 当前类:org.apache.ibatis.executor.BaseExecutor
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除一级缓存,为什么是一级,因为我们现在讲解的insert语句,可以看select语句的执行就知道了
clearLocalCache();
return doUpdate(ms, parameter);
}
进入doUpdate方法
// 当前类:org.apache.ibatis.executor.SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//返回一个StatementHandler对象,工具类
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//返回Statement对象,很简单
stmt = prepareStatement(handler, ms.getStatementLog());
// 插入数据
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
// 当前类:org.apache.ibatis.executor.statement.PreparedStatementHandler
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 接下来就是jdbc的代码了
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
进入newStatementHandler方法
// 当前类:org.apache.ibatis.session.Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
// 返回StatementHandler,构造方法如下,根据不同策略生成StatementHandler对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 这里就是对statementHandler进行插件的增强,和Executor哪里一模一样
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// 当前类:org.apache.ibatis.executor.statement.RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// 假如是这个对象
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
进入PreparedStatementHandler的构造方法
// 当前类:org.apache.ibatis.executor.statement.PreparedStatementHandler
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
进入父类的构造方法
// 当前类:org.apache.ibatis.executor.statement.BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 这里生成ParameterHandler的代理对象,插件进行增强的
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 这里生成ResultSetHandler的代理对象,插件进行增强的
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
进入newParameterHandler方法
// 当前类:org.apache.ibatis.session.Configuration
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 和其他插件的处理一模一样
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
到这里我们就看到了一个sql执行的全过程,也看到了插件的处理原理,下面是官网原话。
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
总结
Mybaits的源码就分析完了,相对于Spring来说,它的源码很通俗易懂,特别适合准备开始研究源码的人阅读。祝愿大家能在源码中学到更多的知识和思想。