说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。
手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为IOC/DI 思想
、Spring MVC
、AOP 思想
、Spring JDBC
四个模块,并且每个模块都会分为理论篇
、源码篇
、实战篇
三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架 ,促进大家进一步了解Spring的本质!
由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》
1.从Mybatis的入口开始
在上一篇理论篇手撸Spring系列11:MyBatis(理论篇)中,我们了解到了,MyBatis的配置文件是由SqlSessionFactoryBuilder
开始读取的,那么我们就从这个类开始,展开我们对MyBatis源码的研究。
1.1.SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
/** 第一个build */
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
/** 第二个build */
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
/** 第三个build */
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
/** 第四个build */
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
/** 第五个build */
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
/** 第六个build */
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
/** 第七个build */
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
/** 第八个build */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
/** 第九个build */
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
在该类中,一共有九个build
方法,但过半的方法都只是调用第四、八或九个build方法而已,自身并没有处理实际的逻辑操作。因此,我们将目光的重点移到第四、八和九中,其中,第四个build与第八个build相比,区别其实就只是前者使用了字符流的方式读取配置信息,而后者使用字节流读取罢了。我们从中选择一个方法研究即可。
那么我们的重心就主要放在 第八个build
和 第九个build
中。
- 第八个
build
:实例化了XMLConfigBuilder
对象并调用其parse()
方法 - 第九个
build
:实例化了DefaultSqlSessionFactory
工厂对象
1.2.XMLConfigBuilder#parse
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return 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);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
在这个方法中,会调用方法parseConfiguration
来解析配置文件的信息,除了最基本的数据库连接信息和环境信息的解析外,还会解析映射文件的信息,我们看到mapperElement(root.evalNode("mappers"));
方法中来,调用的这一个方法就是用来解析映射信息的。
1.3.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 1.以package的方式配置
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");
// 2.以resource的方式配置
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
// 3.以url的方式配置
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
// 4.以class的方式配置
} 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.");
}
}
}
}
}
进入到mapperElement
方法的源码,我们可以发现在该方法中有着四个if判断语句,其实这四个代码块分别是定位映射文件的四种不同的形式。
package
:扫描该包下的所有mapperresourse
:使用类路径定位资源url
:使用url路径定位资源class
:使用类型定位资源
这里我们只探讨第一种形式,package,也就是第一个if语句代码块
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
该代码块中,获取到对应的包名后,以包名为参数传入Configuration
的addMappers
方法中,我们进入到该方法中。
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
进入该方法后发现,最终的任务会交给MapperRegistry
的addMappers
方法去完成,那么我们继续进入该方法的源码进行探究。
1.4.Configuration#addMappers
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 判断mapper是否已在此前注册了
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 注册映射接口
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析映射接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
该方法下主要任务是注册和解析映射接口。在finally
代码块中,会判断loadCompleted
是否为真,从上述代码中其实我们就可以知道,只要MapperAnnotationBuilder
的实例化过程和parse()
执行成功,没有抛出异常的情况下,就会进入到该判断语句中。
即:当映射接口解析成功后,将会在注册表中被remove
掉。
接下来我们进入到MapperAnnotationBuilder
的parse
方法中。
1.5.MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 解析xml文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析缓存对象
parseCache();
// 解析缓存引用,会覆盖之前解析的缓存对象
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 解析方法并生成MappedStatement对象
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
该方法中主要完成xml文件,注解的解析调用,以及为方法生成对应的MappedStatenment
对象。
我们先来看到解析xml文件的loadXmlResource
方法。
1.6.loadXmlResource
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 获取对应的xml配置文件
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
// 将配置文件转为字节流
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
// 解析xml文件,解析过程不做详细分析
xmlParser.parse();
}
}
}
在这里其实我们可以发现,获取与接口相对应的xml配置文件时,是按照全类名加.xml
后缀获取的(如:接口全类名为com.bosen.mapper.TestMapper
,那么它对应的配置文件的路径应该为com/bosen/mapper/TestMapper.xml
)。
接下来我们回到parse
方法中,该方法下还调用了一个核心方法parseStatement(method);
,我们到该方法的源码进行探究。
1.7.parseStatement(Method method)
void parseStatement(Method method) {
// 获取参数类型
final Class<?> parameterTypeClass = getParameterType(method);
// 获取语言驱动对象
final LanguageDriver languageDriver = getLanguageDriver(method);
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
// 用于封装sql语句的SqlSource
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
// 获取sql语句的类型
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
// 获取注解Option
final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
// statement的id由全类名加方法名组成
final String mappedStatementId = type.getName() + "." + method.getName();
// 主键生成对象
final KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
// 如果sql语句属于插入或修改语句进入该代码块
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// 首先检查SelectKey注释,用于生成主键
SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
if (selectKey != null) {
// 初始化keyGenerator
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
// 设置主键属性
keyProperty = selectKey.keyProperty();
} else if (options == null) {
// 如果设置了主键,则使用Jdbc3KeyGenerator,否则使用NoKeyGenerator
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
// 如果option设置了生成主键,则使用Jdbc3KeyGenerator,否则使用NoKeyGenerator
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
// 获取主键属性
keyProperty = options.keyProperty();
// 获取数据库列
keyColumn = options.keyColumn();
}
} else {
// 既不是插入语句也不是修改语句,使用NoKeyGenerator
keyGenerator = NoKeyGenerator.INSTANCE;
}
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
// 是否为查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 不是查询语句,刷新
boolean flushCache = !isSelect;
// 是查询语句,使用缓存
boolean useCache = isSelect;
// 如果使用了options注解,则读取其中的信息
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
String resultMapId = null;
// sql属于查询语句
if (isSelect) {
// 获取返回值类型
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
// 如果存在ResultMap注解,则为返ResultMap生成id
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else {
resultMapId = generateResultMapName(method);
}
}
// 创建MappedStatement对象,并存入到Configuration名为mappedStatements的map中。
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
这个方法代码非常多,相应的它也完成了不少的工作,总结如下:
- 获取参数类型
- 获取语言驱动对象
LanguageDriver
- 创建sql封装对象
SqlSource
- 获取主键生成对象
KeyGenerator
- 创建
MappedStatement
对象,并存入到Configuration
名为mappedStatements
的map中。
以下是一个MappedStatement对象所包含的信息
到了这里,我们可以知道,SqlSessionFactoryBuilder
第八个build
方法的作用其实是解析xml配置文件(或者注解配置),将配置中的sql语句进行封装并生成与之对应的MappedStatement
对象。
2.开始创建会话工厂
/** 第九个build */
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
第九个build方法实例化了一个DefaultSqlSessionFactory
工厂对象,其中,该类的核心方法openSessionFromDataSource
的作用就是用于创建SqlSession
会话对象的。我们进入该方法中。
2.1.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);
// 创建SqlSession会话对象
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();
}
}
在即将创建SqlSession
对象前,都会为其创建一个执行器Executor
。接下来我们进入到DefaultSqlSession
源码中。
2.2.DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
// 省略了很多行代码……
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 省略了很多行代码……
}
在该方法中,有关数据库操作的方法,会调用到上面三个方法:update
、selectList
、selectCursor
,并且最终交给执行器Executor
执行具体操作。
由于类Executor
是一个接口,因此如果我们想要查看执行器是如何执行sql语句的,应该进入到其实现类中查看。笔者这里选用其中的一个实现类SimpleExecutor
来探究。
2.3.SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
// 这里省略了多行代码……
}
在执行器的修改方法doUpdate
中,我们发现,该方法实例化了一个助手类StatementHandler
,而修改操作则是交给这个助手类来完成的。
StatementHandler
同样也只是一个接口,为了探讨该类是如何进行具体的数据库操作的,我们需要进入到其子类的源码中。笔者这里选择其中一个实现类SimpleStatementHandler
来探究。
2.4.SimpleStatementHandler
public class SimpleStatementHandler extends BaseStatementHandler {
// 这里省略了多行代码……
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
// 这里省略了多行代码……
}
在助手类SimpleStatementHandler
中也存在多个方法,这里我们只关注其中的查找方法query
。此方法中,其实就是调用了JDBC中Statement
对象的execute
方法来运行sql语句。
3.获取映射器Mapper
经过上面的操作,我们的Mybatis已经完成了初始化,映射器Mapper也已经准备完成,就等着我们去使用它啦。但,此时问题又来了,我们要如何才可以获取到映射器Mapper?
MyBatis与我们用户之间交互的操作都会通过会话对象SqlSeesion
来完成,此时我们回看到DefaultSqlSession
的源码。
3.1.DefaultSqlSession
其中有个获取mapper的方法getMapper
,看到这个方法中,发现其中调用了Configuration
的getMapper
方法。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
继续进入到该方法中,又发现其调用了mapperRegistry
的getMaper
,我们再继续进入到该方法中。
3.2.MapperRegistry
public class MapperRegistry {
// 这里省略了多行代码……
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
// 这里省略了多行代码……
}
来到这里,发现方法最终会创建一个mapper代理工厂
3.3.MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
该工厂中的实例化代理的方法newInstance
中,创建了代理类MapperProxy
,我们进入到改代理类源码中。
3.4.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
进入到代理类MapperProxy
,发现该类实现了接口InvocationHandler
,表明该代理类使用JDK动态代理来创建的。
我们继续顺着cachedInvoker
探讨MyBatis的代理类最终是如何进行操作的。
3.5.cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
// 这里判断方法是否是default方法,java8的新特性,
// 这里我们不做分析,直接看else代码块
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// PlainMethodInvoker用于封装MapperMethod
// 而MapperMethod用于保存保存对应接口方法的SQL以及入参和出参的数据类型等信息
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
该方法下,我们主要关注else
代码亏块的代码即可,在该代码块中,实例化了MapperMethod
对象,和PlainMethodInvoker
对象。
-
PlainMethodInvoker
用于封装MapperMethod
-
而
MapperMethod
用于保存保存对应接口方法的SQL
以及入参和出参的数据类型等信息
最终PlainMethodInvoker
对象中的invoke
方法会得到调用
3.6.PlainMethodInvoker
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
在该类invoke
方法中,调用了mapperMethod
的execute
方法,进入到该对象的方法源码中。
3.7.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
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;
}
来到这里,我们终于发现,MyBatis的代理类“绕了多个弯子”后,最后是通过调用会话对象SqlSession
的方法实现增、删、改、查操作的,而SqlSession
则将具体的sql执行任务交给JDBC中的Statement
来完成。
至此,MyBatis的源码执行流程我们就分析到这里,下一篇实战篇中,我们将来尝试实现迷你版的MyBatis,并和我们之前实现的迷你版Spring进行整合,使得我们的迷你版Spring功能更加完善~!!