MyBatis
一款優秀的持久層框架,它支持定制化 SQL、存盤程序以及高級映射,MyBatis 避免了幾乎所有的 JDBC 代碼和手動設定引數以及獲取結果集,MyBatis 可以使用簡單的 XML 或注解來配置和映射原生型別、介面和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄,(摘抄至官網)
前言:如果您對MyBatis的底層感興趣,想知道發起一條Sql陳述句執行,底層走了什么操作,那您可以花點時間,認真閱讀下本篇文章,相信會給你不少識訓的,本篇文章步驟條例很清晰的,
MyBatis底層是怎么運行的呢?
public static void main(String[] args) throws IOException {
String configName = "mybatis_config.xml";
Reader reader = Resources.getResourceAsReader(configName);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
1:啟動,加載組態檔
- new SqlSessionFactoryBuilder().build(reader),SqlSessionFactoryBuilder創建出SqlSessionFactory,reader引數接收一個mybatis-config.xml的流檔案,
- 創建 XMLConfigBuilder:config.xml決議器,
- 實體化 XMLConfigBuilder父類(BaseBuilder)的Configuration類,
- 決議config.xml資料,加載到Configuration物件中,
- new DefaultSqlSessionFactory(config) 創建一個SqlSessionFactory實體,默認是DefaultSqlSessionFactory,
如圖所示

1.1:new SqlSessionFactoryBuilder().build(reader)
創建SqlSessionFactory物件實體

1.2:new XMLConfigBuilder(reader, environment, properties);
決議 mybatis-config.xml檔案
public class XMLConfigBuilder extends BaseBuilder {
/* 標記是否已經決議過組態檔 */
private boolean parsed;
/* 決議器 */
private final XPathParser parser;
/**
* 資料源,SqlSessionFactoryBuilder.build(Reader reader, String environment, Properties properties)
* 不指定為空
*/
private String environment;
public XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
/* 初始化 Configuration */
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
/* 設定格外的屬性 */
this.configuration.setVariables(props);
/* 標記初始化為 false */
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
1.3:new Configuration()
創建 Configuration 實體
public class Configuration {
/**
* 型別別名注冊
* 比如 <dataSource type="POOLED">
* 其中的 type="POOLED" 會使用 PooledDataSourceFactory類創建資料源連接池
*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
public Configuration() {
/**
* JDBC 對應使用的 事務工廠類
* <transactionManager type="JDBC"></transactionManager>
*/
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
/**
* MANAGED 對應使用的 事務工廠類
*/
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
/**
* JNDI 對應使用的 資料源
*/
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
/**
* POOLED 對應使用的 資料源
* <dataSource type="POOLED">
*/
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
/**
* UNPOOLED 對應使用的 資料源
*/
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);
}
}
1.4 XMLConfigBuilder.parse()
決議 mybatis-config.xml

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
/**
* 決議 <properties resource="my.properties" /> 組態檔
*/
propertiesElement(root.evalNode("properties"));
/**
* 決議settings組態檔
* <settings>
* <setting name="logImpl" value="STDOUT_LOGGING"/>
* </settings>
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
/**
* 決議 typeAliases組態檔
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 決議 plugins 組態檔
* 這個是插件,可以動態的攔截sql執行
*/
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"));
/**
* 加載 mapper.xml 檔案
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
1.4.1: mapperElement(root.evalNode(“mappers”));
決議mapper.xml檔案,以及介面方法的注解
public class XMLConfigBuilder extends BaseBuilder {
/**
* mapper隱射檔案決議
* @param parent
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
/**
* 決議 <package
* <mappers>
* <package name="com.mapper"/>
* </mappers>
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
/**
* 添加所有包下的介面
* 實際呼叫 configuration.addMapper(mapperInterface);
*/
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
/**
* 決議 <mapper resource
* <mappers>
* <mapper resource="mapper/UserMapper.xml"/>
* </mappers>
*/
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
/**
* mapper.xml 決議器
*/
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
/**
* 開始決議 mapper.xml檔案
* 重點分析這個 同樣也會呼叫 configuration.addMapper(mapperInterface); 這個方法
*/
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
/**
* 決議 <mapper url
*/
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) {
/**
* 決議 <mapper class
*/
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
/**
* 以下代碼可以看出 url resource class 三個屬性只能選擇一個,否則就會報錯
*/
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
}
1.4.2:mapperParser.parse();決議
我的UserMapper.xml檔案內容為:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">
<select id="findById" resultType="com.entity.User">
select * from `user` where userId = #{userId}
</select>
</mapper>
UserMapper介面內容為:
package com.mapper;
import com.entity.User;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("select * from `user` where userId = 2")
User findById(int userId);
}
疑問?UserMapper.xml有<select id=“findById”,而在介面中的findById方法我又加了一個@Select注解;那么執行會選擇哪一條Sql執行還是報錯呢?
以UserMapper.xml為例子決議,可以看到 resource = mapper/UserMapper.xml

public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
/**
* 決議 mapper.xml檔案內容
*/
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
/**
* 決議 mapper.xml的<mapper namespace="com.mapper.UserMapper">
* namespace指定的{UserMapper}介面的注解資訊
*/
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
1.4.3:configurationElement(parser.evalNode("/mapper"));
決議 mapper.xml檔案內容
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
/**
* namespace屬性
*/
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
/**
* 不指定 namespace會報錯哦 由此得知 namespace屬性是必須指定的
*/
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
/**
* 決議 cache-ref
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 決議 cache
*/
cacheElement(context.evalNode("cache"));
/**
* 決議 parameterMap
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 決議 resultMap
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 決議 sql
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 決議 sql陳述句 select|insert|update|delete
* 重點分析這里,這里的決議會關聯到 mapper介面的執行方法 sql陳述句映射
*/
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);
}
}
}
1.4.4:buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));
決議 sql陳述句 select|insert|update|delete;list引數內容是select|insert|update|delete的Sql陳述句
XMLStatementBuilder Sql陳述句的決議器

1.4.5:statementParser.parseStatementNode();決議Sql陳述句
builderAssistant.addMappedStatement,并不是添加一個mapper.xml檔案隱射的實體,而是為每一個Sql陳述句創建一個實體
public class XMLStatementBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
public void parseStatementNode() {
/**
* 此處省略一大推代碼...
*/
/**
* <select id="findById" resultType="com.entity.User">
* select * from `user` where userId = #{userId}
* </select>
* 引數決議
*
* id:標簽指定的id = findById
* sqlSource:Sql陳述句,Sql引數占位
* statementType:sql執行型別 參考{@link StatementType}
* STATEMENT: 直接操作sql,不進行預編譯 ${}
* PREPARED: 預處理,引數,進行預編譯 #{}
* CALLABLE: 執行存盤程序
* sqlCommandType:sql陳述句型別 參考{@link SqlCommandType}
* UNKNOWN:未知,INSERT:新增,UPDATE:修改,DELETE:洗掉,SELECT:查詢,FLUSH:重繪
*
* 其他引數可查看官網:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
1.4.6:builderAssistant.addMappedStatement();
創建一個MappedStatement實體添加到 Configuration.mappedStatements的Map集合中
public class XMLStatementBuilder extends BaseBuilder {
public MappedStatement addMappedStatement() {
/**
* 此處省略一大推代碼...
*/
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
/**
* 建造者模式
* 用于設定 MappedStatement的屬性
* 此處省略一大推代碼...
*/
/**
* 設定引數入參型別 parameterType屬性
* <select id="findById" parameterType="int" resultType="com.entity.User">
* select * from `user` where userId = #{userId}
* </select>
*/
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
/**
* 創建一個 {@link MappedStatement} 實體
*/
MappedStatement statement = statementBuilder.build();
/**
* MappedStatement實體添加到 {@link #configuration.mappedStatements} Map集合中
* MappedStatement 是對應一個Sql陳述句的實體物件
*
* configuration.mappedStatements 存放所有的MappedStatement實體,后面會詳細介紹
*/
configuration.addMappedStatement(statement);
return statement;
}
}
以上流程執行完后,又回到1.4.2:mapperParser.parse();決議,現在開始決議 namespace指定的介面的注解資訊,并創建該介面的代理工廠物件,UserMapper介面,

1.4.7:bindMapperForNamespace();
開始決議介面注解,并添加一個MapperProxyFactory代理工廠的物件到configuration.mapperRegistry.knownMappers;key是Mapper介面
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
/**
* java 反射 Class.classForName
*/
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
/**
* 這里并沒有拋出例外,說明 namespace 可以指定一個不存在的介面
*/
//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.mapperRegistry.knownMappers集合中
* 參考 {@link Configuration#mapperRegistry},
* {@link MapperRegistry#knownMappers}
*/
configuration.addMapper(boundType);
}
}
}
}
}
1.4.8:configuration.addMapper(boundType);
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
/**
* mapperRegistry = {@link MapperRegistry} mapper介面注冊器,存放所有的mapper介面資訊
*/
mapperRegistry.addMapper(type);
}
}
mapperRegistry.addMapper(type);
為Mapper介面創建一個代理工廠,方便后期使用Mapper介面時創建代理類
決議mapper介面的注解資訊
public class 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 {
/**
* {@link MapperProxyFactory} 代理介面工廠
*/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
/**
* {@link MapperAnnotationBuilder} mapper介面注解決議器
*/
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
/* 開始決議 */
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
1.4.9 parser.parse(); MapperAnnotationBuilder.parse()
決議mapper介面的注解資訊,parseStatement(method)主要在這個方法中完成
public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
/**
* 決議 @CacheNamespace 注解
*/
parseCache();
/**
* 決議 CacheNamespaceRef 注解
*/
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
/**
* 決議Sql相關注解 列如 @Select|@Update 之類的注解
*
* 重點關注
*/
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
/**
* 決議待定方法
*/
parsePendingMethods();
}
}
1.5.0:parseStatement(method);
創建一個MappedStatement實體添加到 Configuration.mappedStatements的Map集合中
public class MapperAnnotationBuilder {
private final MapperBuilderAssistant assistant;
void parseStatement(Method method) {
/**
* 此處省略一大推代碼...
*/
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,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
1.5.1:assistant.addMappedStatement();
這里可以參考1.4.6:builderAssistant.addMappedStatement();一模一樣的操作
都呼叫了configuration.addMappedStatement(statement);

這里重點分析Configuration.addMappedStatement(statement);在做什么操作,并且解決 1.4.2留下的疑點;UserMapper.xml和UserMapper介面都有findById的Sql陳述句定義
public class Configuration {
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}
mappedStatements.put(ms.getId(), ms); 實際呼叫 Configuration.StrictMap.put()方法
Configuration.StrictMap是一個重寫的HashMap,put方法會先校驗key是否存在
public class Configuration {
/**
* mappedStatements Sql陳述句的物件
*
* Configuration.StrictMap 實作了HashMap
*/
protected final Map<String, MappedStatement> mappedStatements = new Configuration.StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected static class StrictMap<V> extends HashMap<String, V> {
@Override
@SuppressWarnings("unchecked")
public V put(String key, V value) {
/**
* key 是否存在 存在就拋出例外
*
* 由此得知,方法映射Sql陳述句只能定義一個,要么在 mapper.xml中定義,要么就注解定義
*/
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key
+ (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
}
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, (V) new org.apache.ibatis.session.Configuration.StrictMap.Ambiguity(shortKey));
}
}
return super.put(key, value);
}
}
}
debug除錯,key已經存在,就會報錯,由此得知,方法映射Sql陳述句只能定義一個,要么在 mapper.xml中定義,要么就注解定義

到此,MyBatis的啟動流程就走完了,
2:接下來就來看下是如何執行Sql查詢的,
public class Main {
public static void main(String[] args) throws IOException {
String configName = "mybatis_config.xml";
Reader reader = Resources.getResourceAsReader(configName);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
/**
* 獲取一個會話連接
*/
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* 拿到 UserMapper 的代理類
*/
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
/**
* 執行Sql查詢
*/
User user = userMapper.findById(1);
System.out.println(user);
}
}
輸出結果:User{userId=1, username=‘張三’, sex=‘男’, age=12}
一行代碼的查詢,底層既然走了那么多流程;
流程圖:

2.1:sqlSessionFactory.openSession();打開會話連接
呼叫DefaultSqlSessionFactory.openSessionFromDataSource();
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
/**
* mybatis_config.xml配置的
* <environment>資料源<environment/>
*/
final Environment environment = configuration.getEnvironment();
/**
* transactionManager 配置的事務管理器工廠 type="JDBC" {@link JdbcTransactionFactory}
*<environments default="developmentss">
* <environment id="developmentss">
* <transactionManager type="JDBC"></transactionManager>
* </environment>
* </environments>
*/
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
/**
* 創建事務管理器,由于上面指定的事務管理器工廠是 {@link JdbcTransactionFactory}
* 所以創建的事務管理器是 {@link JdbcTransaction}
*
* @param level 事務隔離級別
* @param autoCommit 是否自動提交事務
*/
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/**
* 創建Sql執行器
*
* @param execType 創建執行器型別 defaultExecutorType如果不指定 默認就是 SIMPLE
* <settings>
* <setting name="defaultExecutorType" value="SIMPLE"/>
* </settings>
*/
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();
}
}
}
2.2:configuration.newExecutor(tx, execType);創建執行器
public class Configuration {
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);
/**
* 執行器會重用預處理陳述句(PreparedStatement)
*/
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
/**
* 普通的執行器 也是默認的執行器
*/
executor = new SimpleExecutor(this, transaction);
}
/**
* 如果開啟了二級快取 cacheEnabled,創建一個CachingExecutor快取執行器
* cacheEnabled 默認為true
* <settings>
* <setting name="cacheEnabled" value="true"/>
* </settings>
*/
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
2.3:sqlSession.getMapper(UserMapper.class);
獲取Mapper介面代理類實體
public class DefaultSqlSession implements SqlSession {
@Override
public <T> T getMapper(Class<T> type) {
/**
* 用 Configuration 類的 getMapper方法
*/
return configuration.getMapper(type, this);
}
}
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
/**
* 呼叫 MapperRegistry Mapper介面注冊器
*/
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
/**
* knownMappers 從快取中獲取 MapperProxyFactory Mapper介面代理工廠
* 如果沒有找到就會拋出例外,
* 說明獲取Mapper介面代理實體時,需要事先定義好 --> 相當于Spring的掃包
*/
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);
}
}
}
2.3.1:使用JDK代理創建Mapper介面代理
InvocationHandler是MapperProxy
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
/**
* MapperMethod 快取
*/
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
/**
* JDK 生產代理類
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
/**
* 代理類回呼介面
*/
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
2.4:userMapper.findById(1); 呼叫Mapper介面方法Sql查詢
會走代理類 MapperProxy.invoke
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@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 if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
/**
* 根據方法全限名 獲取一個MapperMethod實體,并且快取
*
* 注意:這里的 methodCache 只是一個參考,快取的所有物件都在 {@link MapperProxyFactory#methodCache}中
*/
final MapperMethod mapperMethod = cachedMapperMethod(method);
/**
* 開始執行
*/
return mapperMethod.execute(sqlSession, args);
}
/**
* 添加到快取 methodCache {@link MapperProxyFactory#methodCache}中
* computeIfAbsent HashMap 存在就獲取,不存在就新增
* @param method
* @return
*/
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
2.5:mapperMethod.execute(sqlSession, args);
執行Sql陳述句查詢,由于我的回傳結果是一個 User物件,所以會走到
result = sqlSession.selectOne(command.getName(), param);這一行,查詢一條記錄
實際走到 DefaultSqlSession.selectOne()
public class MapperMethod {
private final org.apache.ibatis.binding.MapperMethod.SqlCommand command;
private final org.apache.ibatis.binding.MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new org.apache.ibatis.binding.MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new org.apache.ibatis.binding.MapperMethod.MethodSignature(config, mapperInterface, method);
}
/**
* 開始執行Sql查詢
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 省略代碼... 執行 insert陳述句 <insert>/@Insert
break;
}
case UPDATE: {
// 省略代碼... 執行 insert陳述句 <update>/@Update
break;
}
case DELETE: {
// 省略代碼... 執行 delete陳述句 <delete>/@Delete
break;
}
case SELECT: // 執行 select陳述句 <select>/@Select
if (method.returnsVoid() && method.hasResultHandler()) {
// 回傳型別是否為空 一般情況做Sql操作都要有回傳結果的
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
/**
* 是否回傳多個結果集 {@link Collection}集合/陣列
*/
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
/**
* 回傳型別是否為 Map 集合
*/
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
/**
* 回傳型別是否是 游標 {@link org.apache.ibatis.cursor.Cursor}
*/
result = executeForCursor(sqlSession, args);
} else {
/**
* 將引數轉換為Sql命令引數
*/
Object param = method.convertArgsToSqlCommandParam(args);
/**
* 發起查詢 呼叫的是 sqlSession中的方法
*/
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;
}
}
DefaultSqlSession.selectOne()可以看出實際是呼叫selectList(),而且如果回傳了多個結果集就會報錯,錯誤資訊如下
Expected one result (or null) to be returned by selectOne(), but found: 2

2.6:DefaultSqlSession.selectList()
查詢多結果集
public class DefaultSqlSession implements SqlSession {
/**
*
* @param statement 方法全限名 比如:com.mapper.UserMapper.findById
* @param parameter 引數
* @param rowBounds 分頁
* @param <E>
* @return
*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/**
* 根據方法全限名在 configuration.mappedStatements快取集合中拿到方法對應的Sql Statement物件實體
*/
MappedStatement ms = configuration.getMappedStatement(statement);
/**
* 使用執行器執行 由當前設定的執行器執行
* <setting name="defaultExecutorType" value="SIMPLE"/>
*
* <setting name="cacheEnabled" value="true"/>
* 注意:cacheEnabled由于開啟二級快取默認為true,會先使用 {@link CachingExecutor} 快取執行器查詢
*/
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();
}
}
}
2.7:executor.query執行器查詢
執行器的創建查看 2.2:configuration.newExecutor(tx, execType);創建執行器
/**
* 二級快取執行器
*/
public class CachingExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/**
* 獲取指定的二級快取
* mapper.xml 指定的 <cache type="com.domain.something.MyCustomCache"/>
* Mapper介面的 @CacheNamespace
*/
Cache cache = ms.getCache();
if (cache != null) {
/* 是否需要重繪二級快取 */
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
/**
* 獲取二級快取
*/
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
/**
* 如果沒有資料查詢Sql
*/
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
/**
* 設定快取
*/
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
/**
* delegate = SimpleExecutor
*/
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
2.8:delegate.query(); delegate = SimpleExecutor
因為我沒有指定二級快取,所以直接走向delegate.query, 因為SimpleExecutor繼承了BaseExecutor但是沒有重寫query方法,所以走的是BaseExecutor.query()
public abstract class BaseExecutor implements Executor {
@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.");
}
/**
* 是否需要清空一級快取 flushCache設定為true生效
* <select flushCache="true"></select>
*/
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// 防止多執行緒重復呼叫處理
queryStack++;
/**
* localCache.getObject(key) 獲取一級快取
* {@link BaseExecutor.localCache} 型別 org.apache.ibatis.cache.impl.PerpetualCache
*/
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 (org.apache.ibatis.executor.BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
}
2.9:queryFromDatabase(),
資料庫查詢 會執行到子類的doQuery()方法,這里的字類是SimpleExecutor 所以執行 SimpleExecutor.doQuery()
public abstract class BaseExecutor implements Executor {
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()方法
*
* 這里的字類是SimpleExecutor 所以執行 SimpleExecutor.doQuery()
*/
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;
}
}
2.9.1:SimpleExecutor.doQuery()
執行資料庫查詢
public class SimpleExecutor extends BaseExecutor {
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();
/**
* 創建 RoutingStatementHandler Sql陳述句執行型別處理器
*/
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
/**
* 創建 java.sql.Statement
*/
stmt = prepareStatement(handler, ms.getStatementLog());
/**
* 發起資料庫查詢
*/
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
2.9.2:configuration.newStatementHandler();
創建 RoutingStatementHandler Sql陳述句執行型別處理器
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
/**
* 簡單的Sql執行處理器
*/
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
/**
* 預編譯的Sql執行處理器
* 這是默認的
*/
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
/**
* 存盤程序執行的Sql執行處理器
*/
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
2.9.3:prepareStatement()
創建 java.sql.Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
3.0:handler.query(stmt, resultHandler);
delegate 參考2.9.2:configuration.newStatementHandler()
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
/**
* 當前的 delegate = PreparedStatementHandler
*/
return delegate.query(statement, resultHandler);
}
}
3.1:delegate.query(statement, resultHandler);
執行 PreparedStatementHandler.query()
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
/**
* 發起資料庫查詢
*/
ps.execute();
/**
* 封裝結果集 resultSetHandler = DefaultResultSetHandler
*/
return resultSetHandler.handleResultSets(ps);
}
}
3.2:resultSetHandler.handleResultSets(ps);
封裝結果集
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 封裝結果集
* @param stmt
* @return
* @throws SQLException
*/
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
/**
* 包裝 ResultSet
*/
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
/**
* 包裝成 List
*/
return collapseSingleResultList(multipleResults);
}
}
到這里MyBatis的Sql查詢就結束了,同理添加,修改,洗掉也是同樣的流程,
可以自己跟著以上步驟,斷點除錯追蹤下原始碼,一切迷霧都將會撥開的,
總結:
- 核心依賴于動態代理模式創建Mapper介面代理實體
- SqlSessionFactory負責創建SqlSession,全域唯一實體(類似于單例)
- Configuration 核心中的核心,走到哪里都有它的存在,而且是全域唯一實體(類似于單例)
下一篇分析下MyBatis的核心類,及它們的作用,
覺得對您有幫助,就點個贊唄,😀
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/224784.html
標籤:java
上一篇:簡易ATM源代碼及運行結果
下一篇:JAVA學習筆記(四)城堡游戲
