Mybatis之SqlSession源码解析

之前写过Mybatis之SqlSessionFactory源码解析,但在写这篇文章的时候,发现之前写不是那么好理解,没有给人一种看了就懂的感觉,所以在写这篇文章的时候,会穿插的介绍之前已经分析过的知识和源码,争取这次能够看完这篇文章能够深入了解其内部是怎么实现以及Mybatis的优势。

概述

我们继续借用上次使用过的Mybatis整体的一个流程图,大体的讲下每个节点被用作什么,之后再详细的分析每个节点的作用以及实现原理。
Myabtis流程图

  1. SqlSessionFactoryBuilder.build(in): MyBatis整体入口,通过该方法会最终构建一个SqlSessionFactory(默认是DefaultSqlSessionFactory)实例,SqlSessionFactory用于获取SqlSession,并且SqlSessionFactory中包含着一个非常重要的属性-Configuration实例,Configuration实例贯穿着整个MyBatis框架,这里先了解下,之后会详解Configuation的功能和特点。
    重点: Configuration
  2. factory.openSession(): 获取SqlSession实例,该方法主要是通过SqlSessionFactory()实例获取到一个用于执行具体的SQL方法的对象-DefaultSqlSession。SqlSession实例包含了Configuration实例,该连接的事务级别以及执行该次方法的执行器Executor。
    Executor(执行器):用于调用具体的方法的顶层接口,提供包括查询、更新、事务提交、回滚等方法。在SqlSession实例中会根据入参exeType创建相应的Executor执行器,包括SimpleExecutor、ReuseExecutor、BatchExecutor执行器,从这可以看出Executor采用了策略模式实现。
  3. sqlSession.selectOne()/update()/insert()/delete():调用数据库查询/更新/删除方法。其内部是通过Executor相应的方法。
  4. executor.doQuery()/doUpdate(): executor是具体的执行器,它会在方法体内根据statementId获取到RoutingStatementHandler实例,RoutingStatementHandler中封装了具体的StatementHandler以及一些公用的方法,因此RoutingStatementHandler使用了类似装饰模式的特点。并且Executor执行器在这一步会获取到SQL,并且做参数的解析和封装,最后调用StatementHandler执行最后的操作。
  5. statement.execute():
  6. resultHandler.handleResultSets():

源码分析

Configuration

Configuration是mybatis-config.xml和mapper.xml在Java对象的映射,是MyBatis至关重要的一个对象,贯穿整个MyBatis。Configuration包括默认的属性配置,关键类实例化以及保存statementId和MapperStatement、resultId和ResutMap映射关系。部分源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class Configuration {

//对应mybatis-config.xml中的environment映射
protected Environment environment;

protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
/**
* 保存mapper.xml中select/update/delete/insert的映射关系
* key: mapper.xml中的namespace+elementId
* value: element所对应的Java实体关系
* /
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
/**
* mybatis二级映射的的缓存映射
* key: mapper.xml中的namespace
* value: 客户自定义的Cache实例
* /
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
/**
* 实体与数据库输出数据映射
* key: mapper.xml中namespace+resultMap标签的Id属性
* value: resultMap对应的ResultMap实例
* /
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
//之后的版本该配置已淘汰,所以不做分析
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
//主键生成策略
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

public Configuration(Environment environment) {
this();
this.environment = environment;
}

public Configuration() {
//别名注册
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
}

上述便是Configuration的一些比较常用的属性以及之后在做后边的源码分析时会用到的相关属性,其中最为重要的便是mappedStatements和resultMaps,如果开启了二级缓存则caches也会使用到。我们并没有分析mappedStatements和resultMaps的数据来源,这个在Mybatis之SqlSessionFactory中已经详细介绍过了,这里就不做分析了。Configuration实例除了上述规定后续操作使用到的属性之外,还提供了一些用于创建具体实例的方法,类似于工厂一样,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 创建输入参数处理类,用于处理Sql中所需的参数
* 根据MapperStatement中的lang属性返回具体的输入参数处理类
*/
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

/**
* 创建输出结果的处理类,用于对Statement执行的结果进行处理。
* 返回默认的输出处理类。
*/
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

/**
* Statement是操作JDBC实例,而StatementHandler主要目的是创建Statement实例的一个处理类。
* 默认创建RoutingStatemntHandler类,RoutingStatementHandler没有在后续使用,其主要目的是根据statementType创建具体的StatementHandler对象(包括:prepareStatementHandler、CallStatementHandler、SimleStatementHandler)
* /
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

/**
* 创建默认的执行器,默认的执行器是SimleExecutor
*/
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}

/**
* 根据openSqlSession()获取传入的executorType获取相应的Executor。
* SimleExecutor: 默认的执行器
* ReuseExecutor: 会将Statement对象存储,然后复用
* BatchExecutor: 主要针对于执行存储过程和批量处理的操作。
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : 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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

上边Configuration类似于一个工厂,创建后续会使用到的处理类,包括:Executor(执行器),用于创建StatementHandler和管理StatementHandler生成的Statement对象;StatementHandler用于创建包装后的Statement实现类;ParameterHandler用于处理SQL的输入参数;ResultHandler用于处理Statement的返回结果。上述只是简单的创建处理类的方法,之后小节我们会一一讲解上述的处理类和其中所用到的方法。

MappedStatement

MapperStatement是mapper.xml中select/update/insert/update标签(以下针对上述的标签统称为标签,其余会做声明)所对应的Java实体对象,在MyBatis占用着举足轻重的作用,生成MappedStatement的方法在XmlStatementBuilder类中,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public void parseStatementNode() {
//获取标签的属性Id(MappedStatement中id)
String id = context.getStringAttribute("id");
//获取标签支持的数据库Id(MappedStatement中的datatBaseId)
String databaseId = context.getStringAttribute("databaseId");
//判断dataBaseId和DataBaseProvider中声明是否相同,若不相同则返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取标签的名称(无对应属性,主要为判断下述节点标签的类型)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//判断是否是Select标签
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//根据是否是select标签返回是否需要二级缓存和Statement缓存(MappedStatement中的flushCach)
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//根据是否是select标签返回是否启用会话级缓存,如果是则启用(MappedStatement中的useCache)
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//返回嵌套查询的输出结果顺序(MappedStatement中的resultOrdered)
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//获取标签的输入类型字符串
String parameterType = context.getStringAttribute("parameterType");
//根据输入类型字符串解析获取输入类(这里包括对别名的解析)
Class<?> parameterTypeClass = resolveClass(parameterType);
//获取语言驱动,默认是XMLLanguageDriver
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
processSelectKeyNodes(id, parameterTypeClass, langDriver);
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(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//SqlSoucre:后期的sql生成和输入参数的处理类(重要),后边的章节详细讲解(MappedStatement中的sqlSource属性)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//获取标签的执行类型,默认是PrepareStatementType(MappedStatement中的StatementType)
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//获取期望数据库查询返回的数量(MappedStatement中的fetchSize)
Integer fetchSize = context.getIntAttribute("fetchSize");
//获取该标签SQL执行的超时时间
Integer timeout = context.getIntAttribute("timeout");
//获取输入参数映射(已过时)
String parameterMap = context.getStringAttribute("parameterMap");
//获取输出参数类型字符串
String resultType = context.getStringAttribute("resultType");
//和输入参数类型解析一样,会将输出类型字符串解析为Class类型
Class<?> resultTypeClass = resolveClass(resultType);
//输出结果参数映射字符串名
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取insert/update标签SQL自增长所对应的Java对象的属性名
String keyProperty = context.getStringAttribute("keyProperty");
////获取insert/update标签SQL自增长所对应的数据库的字段名
String keyColumn = context.getStringAttribute("keyColumn");
//
String resultSets = context.getStringAttribute("resultSets");
//通过工具类将以上参数去生成MapperStatement对象,并添加到Configuration的mappedStatement集合中。
//builderAssistant是一个针对mapper.xml的一个实例对象工具类,可以共享其中的resultMap、cache、sql等标签的结果
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上述代码则是MappedStatement的生成方法,MappedStatement中的大部分属性在我们的XML配置中都能对应起来,只要掌握了Xml中的配置,这些属性不难理解。但是有几个比较特殊,像SqlSource、StatementType,这些我们都可以在这里先了解下,知道是在这里生成的,我们之后再分析其实现原理和源码。
还有一点我觉得MappedStatement中的实现挺有意思的,就是MapperBuilderAssistant,这个类是在解析mapper.xml时候生成的工具类,主要是为解决同一个命名空间下需要公用的部分和一些m命名空间中需要用的方法,像:<cache>标签,resource资源,创建Cache实例方法,添加ResultMap方法等,体现了设计原则中的单一原则(应该仅有一个原因涉及类的变化),虽然这里不是一个原因,但是也只涉及到命名空间这一层,未涉及其它方面,所以觉得比较好。

SqlSession

      上边的小节将之前讲过的Configuration对象和MappedStatement对象再稍微的讲解了一遍,这对接下来的分析很有帮助。
      在未接触Spring和MyBatis结合之前,我们通常都会使用SqlSessionFactory打开一个SqlSession会话来进行操作,SqlSession提供了执行select、update、insert、update等方法。在之后学习了Spring和MyBatis结合之后,我们使用了mapper接口的形式,其实内部也是使用sqlSession操作,只是获取SqlSession和执行具体SqlSession方法的操作由Spring AOP将其封装起来,这小节我们不关注这个,我们还是先看SqlSession的获取和SqlSession的内部实现。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取当前的环境信息
final Environment environment = configuration.getEnvironment();
//根据环境信息获取当前的事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
/**
* 通过事务工厂创建一个新的事务实例, 默认是JdbcTransaction
* dataSocurce: 数据源
* level: 事务的隔离级别(有5中隔离级别,默认是数据库默认的隔离级别)
* autoCommit: 是否允许自动提交
*/
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器,这个后边在分析sqlSession的内部方法的时候重点分析
final Executor executor = configuration.newExecutor(tx, execType);
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会话的方法,有上述代码可以看出最后返回的是一个DefaultSqlSession实例,其中包括了全局的Configuration配置,执行器Executor以及是否自动提交的属性,并且在之前获取了事务管理对象tx,这里说下事务管理对象tx,我们知道MyBatis默认使用的Jdbc的事务管理配置,虽然有manage的事务管理配置,但是manage配置是将事务管理交给了Spring这种框架进行事务管理,由此在单独使用MyBatis时,都是使用JdbcTranaction这个事务实例,上述也使用的是JdbcTranaction,这里了解一下。
    执行器Executor,之前我们在分析Configuration类的时候有一个newExecutor方法,configuration.newExecutor()方法最后返回的实例主要是根据我们获取SqlSession会话时传进来的executorType参数来判断返回哪一类型的执行器,包括ReuseExecutor、BatchExecutor、SimpleExecutor和CacheingExecutor,CacheingExecutor使用了装饰者模式,将执行创建的执行器包装起来并附加了缓存的功能,最后是否返回CachingExecutor需要根据MyBatis全局配置的cacheEnable属性来决定。 获取Executor源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 根据openSqlSession()获取传入的executorType获取相应的Executor。
* SimleExecutor: 默认的执行器
* ReuseExecutor: 会将Statement对象存储,然后复用
* BatchExecutor: 主要针对于执行存储过程和批量处理的操作。
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//返回批量执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
//返回可重用Statement的执行器
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//默认返回简单执行器
executor = new SimpleExecutor(this, transaction);
}
//如果configuration的setting配置中cacheEnable是开启的,则返回的是一个包装了之前执行器的一个执行器,附加了缓存的功能
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

上边介绍了获取SqlSession的方法,获取SqlSession时又实例化了Executor,接下来便是我们执行我们的查询操作,例如:sqlSession.selectOne(“com.xxx.dao.selectStudent”, student)。那么我们就继续看看SqlSession都提供了哪些方法并且内部的实现,下边的代码是比较重要的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
public class DefaultSqlSession implements SqlSession {

private final Configuration configuration;
private final Executor executor;

private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}

/**
* 获取数据库中单条记录的方法,若获取的记录超过一条返回异常
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* /
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

/**
* 获取数据库中最后返回游标的方法,例如数据库中某些存储过程或函数返回的是个游标
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* RowBounds:返回结果的限定,包括开始记录和数据记录限制
* /
@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();
}
}

/**
* 获取数据库中多条数据记录
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* RowBounds:返回结果的限定,包括开始记录和数据记录限制
* /
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

/**
* 针对于操作数据库中的存储过程和函数,这种将返回结果绑定在了parameter的属性中
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* RowBounds:返回结果的限定,包括开始记录和数据记录限制
* ResultHandler:数据输出结果的处理函数
* /
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

/**
* 插入数据记录,实际内部调用的和update方法一样
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* /
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}

/**
* 修改数据库中的数据记录
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* /
@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();
}
}

/**
* 删除数据库中的数据记录
* statement:mapper.xml中的id(namespace+select标签的id属性)
* parameter:输入参数
* /
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}

//提交事务
@Override
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

//回滚事务
@Override
public void rollback(boolean force) {
try {
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

//刷新Statement,这个主要针对于ReuseExecutor和BatchExecutor
@Override
public List<BatchResult> flushStatements() {
try {
return executor.flushStatements();
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

//关闭会话
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}

//关闭游标
private void closeCursors() {
if (cursorList != null && cursorList.size() != 0) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}
}

上述便是SqlSession提供的方法,其中有部分重载的方法因为篇幅原因没有罗列出来,但是最终调用的也就是上述已列出来的方法,我们只需要分析上述方法即可。通过上边的源码我们可以看到,上边比较重要的是selectList、update、selectCursor、select以及事务操作的相关方法。其中selectList、update、selectCursor、select方法都是先通过statement参数获取configuration全局配置中相对应的MappedStatement实例,然后在调用executor的相关方法,因此内部的操作是由Executor实现的。接下来我们就分析Executor类几个实现类和实现类具体都做了什么。Let’s go!

Executor

    Executor是MyBatis的执行器,主要用于获取Statementhandler,并且通过StatementHandler创建JDBC的Statement对象.Executor有五种实现,其中CachingExecutor使用了装饰者模式提供了缓存的功能;BaseExecutor是一个基础实现,提供了一些公用的方法;SimleExecutor、ReuseExecutor、BatchExecutor三种在BaseExecutor的基础上针对不同场景做了不同的实现。我们接下来一一分析。

BaseExecutor

    在学习BaseExecutor之前,我们先回顾下MyBatis的知识。MyBatis提供了会话缓存和二级缓存,会话缓存指的是的同一个会话中,如果拥有相同的sql和查询条件, 则会返回缓存中的内容而不需要查询数据库。二级缓存和会话缓存相似,只不过二级缓存的作用域是同一个命名空间下。会话缓存就是在BaseExecutor中实现。
    BaseExecutor类提供了很多方法,其中最主要的便是query和update方法,因此我们就以query和update方法作为切入点来分析,首先先看query方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 创建缓存的键
* ms: 标签所对应的MappedStatement实例
* parameterObject:查询参数
* RowBounds: 输出结果限制
* BoundSql:Sql的内部实现
*/
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}

    上边的代码是BaseExecutor查询入口的代码,首先会获取BoundSql实例,这个BoundSql是干什么用的呢,我们先在这里先笼统的记一下这个BoundSql就是一个XMl中标签里的sql语句在java内部的一个映射,在之后分析SqlSource的时候会介绍BoundSql;第二步创建缓存的键,怎么创建那?将MappedStatement实例的键、RowBounds中下标和结果输出数量、Sql语句以及查询参数作为条件,获取上述条件的hashCode,然后按照一定的算法计算出最终的hashCode,并将上述条件保存到集合中,具体的实现自己可以看下源码;第三部调用重载方法查询。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
//如果执行器已关闭,则返回异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果查询是第一次并且标签配置上要求是刷新缓存的,则清空缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {

queryStack++;
//从缓存中获取结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
//针对Callable类型的调用做特殊处理
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//调用查询数据库方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
//queryStack表示这个会话是否还有执行查询,若没有则清空会话缓存
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

/**
* 从数据库中查询方法
* ms:标签对应的MappedStatement实例
* parameter:查询参数
* rowBounds:查询结果的限制条件
* resultHandler:输出结果处理
* key:缓存key
* BoudSql:解析的SQL
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//像缓存中插入一个占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//调用其继承类的doQuery方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//先将缓存中的这个键移除,然后在将结果放入到缓存中
localCache.removeObject(key);
}
localCache.putObject(key, list);
//对CallStatementType的查询额外处理
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

    上述的代码就是最重要的查询方法,可以看出会先判断会话缓存中是否有相同的键,如果有则从缓存中返回,否则查询数据库。queryFromDatabase会调用继承类的doQuery方法并且更新缓存。

update
1
2
3
4
5
6
7
8
9
@Override
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.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}

    update方法相对比较简单,再调用继承类的doupdate方法之前都需要清空会话缓存。
    BaseExecutor最主要的两个方法已经分析完了,我们可以看出MyBatis将会话缓存的部分交给了基础的BaseExecutor实现,而其他执行器值针对自己需要的功能做加工即可,不用关心缓存的处理。

SimpleExecutor

SimpleExecutor是MyBatis若不开启二级缓存的情况下默认返回的实现类。SimpleExecutor提供了简单doUpdate和doQuery方法,我们先看doQuery方法。

doQuery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取全局配置Configuration
Configuration configuration = ms.getConfiguration();
//通过cofiguration创建StatementHandler,这个在之前的Configuration章节介绍过
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//预处理Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取数据库连接
Connection connection = getConnection(statementLog);
/**
* 通过StatementHandler创建Statement对象.
* 对于PrepareStatementHandler的返回PrepareStatement对象
* 对于SimpleStatementHandler返回Statement对象
* 对于CallableStatementHandler返回CallableStatement对象
stmt = handler.prepare(connection, transaction.getTimeout());
//输入参数处理,主要针对PrepareStatementHandler和CallStatementHandler的输入参数处理
handler.parameterize(stmt);
return stmt;
}

    上边的代码就是SimpleExecutor查询的方法,其中的cofiguration.newStatementHandler方法在分析Configuration章节的时候已经介绍过了,Configuration内部虽然返回的是RoutingStatementHandler方法,但是RoutingStatementHandler的构造方法根据不同的statementType返回PrepareStatementHandler、SimpleStatementHandler、CallableStatementHandler实例。
紧接着调用prepareStatement方法,prepareStatement方法内部主要是创建Statement实例,首先创建数据库连接,然后根据不同的StatementHandler创建不同的Statement实例(这个等会分析StatementHandler实例时在分析);然后statement对象做输入参数的绑定;最后返回Statement对象。
    从上述可以看出SimpleExecutor主要是做创建StatementHandler类,通过StatementHandler创建Statement对象,创建Statement对象成功之后,后续的操作即是JDBC的Statement查询、更新。还有一点说明下:ReuseExecutor的doQuery方法和SimpleExecutor是一样的,只是prepareStatement方法的内部实现不同,因此在分析ReuseExecutor和BatchExecutor时,只分析了其prepareStatement方法。

doUpdate
1
2
3
4
5
6
7
8
9
10
11
12
@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方法和doQuery方法内部代码一模一样,因此不在此分析了。

ReuseExecutor

    ReuseExecutor中有一个非常重要的属性statementMap,它的key是执行的sql,value值是Statement实例。通过这个属性才实现了ReuseExecutor的可重用Statement功能。

1
private final Map<String, Statement> statementMap = new HashMap<>();
prepareStatement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取SQL
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
//判断ReuseExecutor中是否已经存在该Sql的Statement,若存在则获取缓存到的Statement为其设置超时时间。
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
//和SimpleStatementHandler一样,获取连接,创建Statement对象
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
//输入参数的绑定
handler.parameterize(stmt);
return stmt;
}

    ReuseExecutor相对于SimpleExecutor只是在prepareStatement方法中首先会从statementMap属性中尝试获取Statement对象,如果获取到则设置超时时间,未获取到则创建Statement对象并将其添加到statementMap属性中。因此从上述可以看出,如果我们在一个sqlSession会话中会多次调用相同sql语句,则可以使用ReuseExecutor,避免了多次创建和销毁Statement对象造成的空间和时间浪费。

BatchExecutor

    BatchExecutor从字面意思就能够看出其主要针对的是批量的操作,批量操作有几个很重要的属性。BatchExecutor和SimpleExecutor的主要不同是doUpdate方法,因此我们着重分析doUpdate方法。

1
2
3
4
private final List<Statement> statementList = new ArrayList<>();
private final List<BatchResult> batchResultList = new ArrayList<>();
private String currentSql;
private MappedStatement currentStatement;
doUpdate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
//获取Sql字符串
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
//如果之前已经有相同的sql字符串并且statement对象相等,则只需要对当前的输入参数进行处理。
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);//fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
//若第一次连接或不满足上述条件,则创建连接和Statement对象,并将当前的Statement对象和sql字符串赋给BatchExecutor对象的属性用于后续判断。
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}

    BatchExecutor的doUpdate方法相对SimpleExecutor方法来说好像复杂很多,但是仔细分析的话会发现还是相对简单的。BatchExecutor主要是针对于相同的SQL和MappedStatement实例,如果Sql字符串和MappedStatement实例相等的话,则获取statementList集合的最后一个Statement对象,并为其处理输入参数,这样便可以针对于拥有相同SQL和MappedStatement对象的批量执行。综上,我们可以针对于循环新增、更新、删除的操作采用BatchExecutor类

StatementHandler

    上边的一个章节分析了Executor的使用以及doQuery/doUpdate方法的内部实现,在doQuery和doUpdate方法内部都使用了configuration.newStatementHandler方法用于创建StatementHandler,那么我们继续分析MyBatis的重要组件StatementHandler。

StatementHandler初始化
1
2
3
4
5
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

    StatementHandler的初始化方法在Configuration类中,在上述方法中看到返回的是一个RoutingStatementHandler实例,RoutingStatementHandler实例的构造方法中会根据MappedStatement的statementType创建相应的StatementHandler。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private final StatementHandler delegate;

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());
}
}

    上边的代码很清晰的表明在RoutingStatementHandler的构造函数会根据statementType创建不同的StatementHandler。RoutingStatementHandler使用了外观模式,使用者不关心内部的实现,可以实现程序解耦并且对外透明,安全性较高。
    我们在日常的开发使用中,使用最多的应该是PrepareStatementHandler,PrepareStatementHandler可以预编译SQL,从而防止SQL注入,提高安全性。因此我们本小节就重点分析PrepareStatementHandler。在上小节中我们知道在Executor执行器的prepareStatement方法中都会调用一个statementHandler.prepare方法,prepare方法的实现在BaseStatementHandler中,是几个StatementHandler公用的方法,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//初始化Statement实例
statement = instantiateStatement(connection);
//设置Statement的执行超时时间
setStatementTimeout(statement, transactionTimeout);
//设置期待返回的结果集数量
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}

    prepare方法中最为重要的是instantiateStatement方法,instantiateStatement是个抽象方法,具体的实现在BaseStatementHandler的继承类中,PrepareStatementHandler的prepare方法实现在下方。setStatementTimeout和setFetchSize是公用方法,设置Statement的执行超时时间和期待返回的数据结果集数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}

    上述代码目的就是为了创建一个Statement对象。对于这个Statement对象需要判断是否需要有自增长的列和输出结果要求进行创建,就不详细分析了,相信使用过JDBC的大家都会对这个connection.prepareStatement方法非常清楚。截至到此,我们分析了在Executor类中调用的prepare方法,紧接着会调用handler.parameterize(stmt)方法,那么我们继续看StatementHandler另一个很重要的方法,封装参数parameterize。

1
2
3
4
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}

    上述代码只有一句话,类似于使用一个专门的参数处理器来处理这个statement对象,通过查看类的继承图发现ParameterHandler只有一个实现类为DefaultParameterHandler,其setParameters(statement)方法如下:

ps)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取SQL语句中被#{}修饰的属性名信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);、
//如果#{}的参数类型不是out,则进行参数类型的处理
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//获取Sql中#{}的参数名称
String propertyName = parameterMapping.getProperty();
//是否拥有这个参数
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
//如果是简单类型或者在MyBatis中设置过类型处理函数,则将对象赋值给value
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
//针对单个实体对象的
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//获取该参数的类型处理函数
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//利用类型处理函数对Statement对象塞值
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

    上述的代码有点复杂,因为牵扯到之后需要介绍到的SqlSource和BoundSql的部分方法,而且MyBatis中因为有ObjectFactory对于创建实体对象的设置,因此代码看起来相对比较困难。详细的逻辑如下:

  • boundSql.hasAdditionalParameter(propertyName):用于获取SQL中#{}的属性的设置。
  • boundSql.hasAdditionalParameter(propertyName): 判断是否拥有这个属性,但是看了Sqlsource和BoundSql的相关源码,还是没有了解这个针对的是什么情况?希望之后有哪位大佬看到了可以给解释下。
  • parameterObject == null: 对于输入参数为空的,则直接将value值设置为空。
  • typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()): 在我们的类型处理器中如果有对应的类型处理函数则将该对象赋值给value,之后使用类型处理函数设置。
  • 否则就是实体对象,这种通过MetaObject获取实体对象中相应的属性值。
  • 最后使用类型处理函数处理相应的类型,并将其设置到statement对象中。
小结

    StatementHandler只分析了其中的PrepareStatementHandler对象。StatementHandler因为相对简单,只是创建了Statement对象,在处理输入参数没有做任何操作,一般我们使用也比较少,所以就不分析这个了,相信掌握PrepareStatementHandler的看到StatementHandler的代码,会觉得非常的简单。CallableStatementHandler主要是针对函数和存储过程的调用,CallableStatementHandler和PrepareStatementHandler方法类似,只是多了一个对于#{}中mode为out类型做了参数处理,其它和PrepareStatementHandler相似,因此也不在这里分析了。StatementHandler除了上述讲的prepare()和instantiateStatement方法,还有query、update、queryCursor方法,这些方法内部使用JDBC进行操作,对于有输出结果的需要通过输出类型函数进行处理。

SqlSource

暂无内容

BoundSql

暂无内容

总结

      本篇详细讲解了MyBatis的整体运行流程和里边一些相对比较重要的组件,但在最后没有分析SqlSource和BoundSql的相关内容,因为我觉得这两个组件相对比较复杂,自己一时半会组织不好语言去详细的讲解清楚这两个组件,所以待之后完善。
      这篇文章写完花了大概两周的时间,倒不是因为自己比较忙或是没时间写的原因,只是在写的过程中总是发现自己当时掌握的有点相对简单,不够深入,也不知道该怎么去整理章节去讲清楚MyBatis中的相关内容,幸运的是最后自己还是写完了这篇文章,也通过写这篇文章让自己明白了很多之前漏掉的内容,让自己更加的深入的了解和使用MyBatis。最后,给自己鼓个劲,虽然写的不够好,但是只要坚持,我相信你一定会做的很好,有句俗话说得好:“只要你一直努力,最坏的结果也只是大器晚成”。加油!