友情提示 : 本文的重點是解析mybatis執行一次sql 的流程 ,過程中思維比較跳躍,覺得比較難得可以往下查看總結中源碼流程
這篇文章通過一個insert語句來解析mybatis執行一次sql的運行流程。背景介紹,源碼來自於 mybatis-3.0.5.jar,
mybatis-config.xml:
<?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>
<properties resource="jdbc.properties" />
<!-- 配置別名 -->
<typeAliases>
<typeAlias type="dao.DataDao" alias="DataDao" />
</typeAliases>
<!-- 配置環境變量 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${driverClass}" />
<property name="url" value="${jdbcUrl}" />
<property name="username" value="${DBusername}" />
<property name="password" value="${DBpassword}" />
</dataSource>
</environment>
</environments>
<!-- 配置mappers -->
<mappers>
<mapper resource="dao/DataDao.xml" />
</mappers>
</configuration>
mapper對應的接口:
public interface DataDao {
public int mysqlInsert(Map<String, Object> map) throws Exception;
}
接口對應的 DataDao.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="dao.DataDao">
<insert id="mysqlInsert" parameterType="map" >
insert into ${tableName}
(${columns})
values
(${colValues})
</insert>
</mapper>
首先我們會從mybatis的入口加載的地方來進行解讀,先看一段mybatis的插入代碼:
/**
* 傳入map參數,執行插入
* @param map
* @return
*/
public static int mysqlInsert(Map<String, Object> map) throws Exception {
SqlSession session = null;
int index=0;
try {
session = SessionFactory.getSession();
DataDao dataDao = session.getMapper(DataDao.class);
index=dataDao.mysqlInsert(map);
if(index>0){
logger.info("insert success: "+index);
}
session.commit(true);
} catch (Exception e) {
logger.error("插入異常:", e);
session.rollback(true);
throw e;
} finally {
session.close();
}
return index;
}
public class SessionFactory {
private static Logger logger = LoggerFactory.getLogger(SessionFactory.class);
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
//初始化mybatis
static {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
} catch (IOException e) {
logger.error("加載配置文件錯誤:",e);
e.printStackTrace();
}catch (Exception e){
logger.error("加載配置文件錯誤:",e);
}
logger.info("init mybatis");
}
public static synchronized SqlSession getSession() throws IOException {
SqlSession sqlSession =null;
if(null == sqlSession) {
sqlSession = (null != sqlSessionFactory)?sqlSessionFactory.openSession(): null;
}
return sqlSession;
}
}
代碼中可以看到首先會通過 SessionFactory 去獲取 SqlSession,裏面會有一段static代碼,在SessionFactory類加載時會執行mybatis-config.xml 的初始化過程。現在我們直接看重點:
通過SqlSession 的 getMapper獲取DataDao數據接口的時候發現有2個實現,追溯到SessionFactory的初始化中會看到下面一段:
所以我們直接進入 DefaultSqlSession getMapper方法中,可以找到 MapperRegistry 中:
看到這裏熟悉的動態代理的朋友知道這是一個獲取java的動態代理類的寫法(ps: 不熟悉動態代理的,可以看這裏我的上一篇博客 動態代理白話解析),我們繼續往下看:
繼續走到下一步:dataDao.mysqlInsert(map); 執行這個方法時會調用 MapperProxy 的 invoke方法:
開源看到除了Object 中的方法不能代理,其他的都會走到 mapperMethod.execute(args):
type 字段會在mapperMethod初始化時賦值,這裏我們執行的insert,繼續往下走到DefaultSqlSession 的update方法 :
到這裏後,接下來我們開始看 Configuration的初始化,它在SqlSessionFactory初始化時構造的
上面2段代碼可以看到 Configuration 是通過加載mybatis-config.xml時進行初始化的。
現在我們回到 this.configuration.getMappedStatement(statement); 這段代碼:
進去看下我們發現他其實是通過 id 去獲取對應的MappedStatement ,通過上文我們可以知道id是MapperMethod的execute方法傳進來的commandName,它在MapperMethod 的構造函數中的 setupFields 方法中初始化:
在本文中的值爲: DataDao.mysqlInsert。類似這種,也就是接口類.方法名。
現在我們回到主流程中,查看 mappedStatements 的初始化過程,是在上文中初始化Configuration 的this.mapperElement(root.evalNode("mappers")); 中 :
這裏會解析mapper.xml中的sql節點,本文中的示例是 這一段:
我們繼續查看mappedStatements的初始化 :
這裏的id我們可以看到就是節點中的namespace.id的值拼接,也就是 DataDao.mysqlInsert
現在我們繼續回到主流程,看這段代碼:
代碼中通過executor執行update方法,現在我們看下 executor 的初始化過程,是在SqlSession初始化的過程中:
在Configuration的構造函數中可以看到這裏的 executorType 默認爲SIMPLE,cacheEnabled 默認爲true.
現在我們繼續回到主流程,進到 CachingExecutor 的 update 方法中:
這裏會進入到 BaseExecutor 的 update方法中
這裏會進入到 SimpleExecutor 的doUpdate中:
這裏首先初始化了一個 RoutingStatementHandler ,由上文可知 statementType 默認爲 PREPARED,所以 其 delegate 屬性爲 PreparedStatementHandler。我們回到主流程,繼續看 this.prepareStatement(handler) :
會走到 BaseStatementHandler 的 prepare 方法 :
這裏會進入到 PreparedStatementHandler 的 instantiateStatement 方法:
這裏的 boundSql 是在 上文中的 mappedStatement 初始化的時候賦值的:
首先是在 parseStatementNode 中初始化 DynamicSqlSource,然後獲取 BoundSql,接下來我們看下XMLStatementBuilder的parseDynamicTags方法:
代碼中我們可以看到若節點類型既不是文本節點也不是CDATA節點就會調用不同的處理類去處理,我們比較常用的就是查詢時使用的 <if> 和 <where> 節點 ,這裏我們執行的是 insert , 所以直接是一個文本節點,下面就是初始化了一個MixedSqlNode 和 configuration 構造了 DynamicSqlSource,然後通過getBoundSql 方法來構造BoundSql:
由上文知:rootSqlNode是MixedSqlNode,這裏會執行 MixedSqlNode 的 apply 方法,在本例中會執行 TextSqlNode 的 apply
方法:
這一段呢主要是用來處理sql中的${}佔位符參數,本文中的 insert into ${tableName} (${columns}) values (${colValues}) 經過parse 轉換後 insert into table (id,name) values (1,'test') , 這一段的代碼就不分析了,比較繁雜,主要是通過將對應的佔位符替換爲對應的值。
我們回到 getBoundSql 方法 ,繼續往下走,到 sqlSourceParser.parse(context.getSql(), parameterType) 這一行代碼:
又看到了熟悉的代碼,這一段是將#{} 佔位符替換爲對應的value.
我們繼續回到上文主流程中的 PreparedStatementHandler 的 instantiateStatement 方法,往下走:
這裏的 Jdbc3KeyGenerator 是如果你設置了useGeneratedKeys=true 用來 自動生成主鍵的,resultSetType 本例中也沒有涉及爲空,所以這裏會返回一個 PreparedStatement, 到這裏我們的 SimpleExecutor 的 doUpdate 的 this.prepareStatement(handler)
這段代碼就分析完了。
我們回到主流程繼續往下走:
這裏進入到 jdbc 的 PreparedStatement 的執行過程,執行insert插入的操作。後面2段代碼我就不分析了,主要是用來獲取主鍵的。
到這裏,mybatis執行一次insert sql的運行機制就解析完成了。
總結:
在源碼分析的過程中,可能會比較跳躍,因爲在源碼分析過程中會有一些配置類或執行類的初始化過程。這裏分享一下快速閱讀源碼的方式,在查看源碼的過程中,可能會有許多擴展,校驗,初始化的一些功能代碼,比如:
1. Configuration 類的初始化
2. Interceptor 攔截器的執行(實際採用的是責任鏈設計模式和動態代理設計模式來實現的攔截)
3. cache緩存的實現方式
可以暫時先跳過,先把主要流程的代碼過一遍,比如mybatis 執行一次sql的流程源碼可以按照如下方式查看:
當我們將整體流程實現查看清楚後,如果想查看更多的細節,可以使用其他時間回頭來查看擴展功能的實現方式