Mybatis工作原理
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 江南入直
來(lái)源 | urlify.cn/7N7nAv
近來(lái)想寫一個(gè)mybatis的分頁(yè)插件,但是在寫插件之前肯定要了解一下mybatis具體的工作原理吧,于是邊參考別人的博客,邊看源碼就開干了。
核心部件:
SqlSession、Executor、StatementHandler、ParameterHandler、ResultSetHandler、TypeHandler、MappedStatement、Configuration
在分析工作原理之前,首先看一下我的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>
<!-- 和spring整合后 environments配置將廢除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務(wù)管理 -->
<transactionManager type="JDBC" />
<!-- 數(shù)據(jù)庫(kù)連接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sqlMapper/userMapper.xml"/>
</mappers>
</configuration>
第一步:創(chuàng)建一個(gè)sqlSessionFactory
在了解如何創(chuàng)建sqlSessionFactory之前,先看一下mybatis是如何加載全局配置文件,解析xml文件生成Configuration的
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 {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
在上面的第二段代碼中有一句
mapperElement(root.evalNode("mappers"));
剛好我們的全局配置文件中有一個(gè)mapper的配置,由此可見(jiàn),mapperElemet()方法是解析mapper映射文件的,具體代碼如下
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
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) {//進(jìn)入該判斷
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
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.");
}
}
}
}
}
根據(jù)以上代碼可以分析,在寫mapper映射文件的地址時(shí)不僅可以寫成resource,還可以寫成url和mapperClass的形式,由于我們用的是resource,所以直接進(jìn)入第一個(gè)判斷,最后解析mapper映射文件的方法是
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
其中具體解析每一個(gè)sql語(yǔ)句節(jié)點(diǎn)的是
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
進(jìn)入這個(gè)方法一層層深究,最后到這里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)創(chuàng)建的。
public void parseStatementNode() {
...
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
最后進(jìn)入方法addMappedStatement(),mappedStatement最后以id為鍵保存在了Configuration中的一個(gè)map變量mappedStatements中。
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.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
最后回到我們的創(chuàng)建sqlSessionFactory上,之前的一切都是為了生成一個(gè)sqlSessionFactory服務(wù)的
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
從上面的代碼可以看出最后是通過(guò)以Configuration為參數(shù)build()方法生成DefautSqlSessionFactory。
第二步:創(chuàng)建sqlSession
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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);
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();
}
}
//返回一個(gè)SqlSession,默認(rèn)使用DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
executor在這一步得到創(chuàng)建,具體的使用在下一步。
第三步:執(zhí)行具體的sql請(qǐng)求
在我的代碼里執(zhí)行的是
User user = sqlSession.selectOne("test.findUserById", 1);
具體到里面的方法就是
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.根據(jù)Statement Id,在mybatis 配置對(duì)象Configuration中查找和配置文件相對(duì)應(yīng)的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢?nèi)蝿?wù)委托給MyBatis 的執(zhí)行器 Executor
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在這里通過(guò)statementId拿到了我們?cè)诘谝徊酱嬖趍ap里面的MappedStatement。在這里引用參考博客的一句話:
SqlSession根據(jù)Statement ID, 在mybatis配置對(duì)象Configuration中獲取到對(duì)應(yīng)的MappedStatement對(duì)象,然后調(diào)用mybatis執(zhí)行器來(lái)執(zhí)行具體的操作。
再繼續(xù)看query()和queryFromDatabase()這兩個(gè)方法
@SuppressWarnings("unchecked")
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;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
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 {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在這兩個(gè)方法里面會(huì)為當(dāng)前的查詢創(chuàng)建一個(gè)緩存key,如果緩存中沒(méi)有值,直接從數(shù)據(jù)庫(kù)中讀取,執(zhí)行查詢后將得到的list結(jié)果放入緩存之中。
緊接著看doQuery()在SimpleExecutor類中重寫的方法
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Statement連接對(duì)象就是在這里創(chuàng)建的,因此Executor的作用之一就是創(chuàng)建Statement了,創(chuàng)建完后又把Statement丟給StatementHandler返回List查詢結(jié)果。
接下來(lái)再看一下這里的兩個(gè)方法prepareStatement()和query()的具體實(shí)現(xiàn)
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
prepareStatement()是創(chuàng)建Statement的具體實(shí)現(xiàn)方法,調(diào)用parameterize()對(duì)創(chuàng)建的Statement對(duì)象設(shè)置參數(shù),即為我們?cè)O(shè)為占位符的地方賦上指定的參數(shù),parameterize()方法再深入進(jìn)去就是調(diào)用ParameterHandler的setParameters()方法具體賦值了。
這里的query()是調(diào)用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合。
總結(jié)以上步驟就是:
1.根據(jù)具體傳入的參數(shù),動(dòng)態(tài)地生成需要執(zhí)行的SQL語(yǔ)句,用BoundSql對(duì)象表示
2.為當(dāng)前的查詢創(chuàng)建一個(gè)緩存Key
3.緩存中沒(méi)有值,直接從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)
4.執(zhí)行查詢,返回List 結(jié)果,然后 將查詢的結(jié)果放入緩存之中
5.根據(jù)既有的參數(shù),創(chuàng)建StatementHandler對(duì)象來(lái)執(zhí)行查詢操作
6.將創(chuàng)建Statement傳遞給StatementHandler對(duì)象,調(diào)用parameterize()方法賦值
7.調(diào)用StatementHandler.query()方法,返回List結(jié)果集
總結(jié)
以上三個(gè)步驟所有流程大體可以用一張圖來(lái)總結(jié)

鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
