為了便于源碼分析,還是先來一個MyBatis的Demo吧
mybatis-mysql-config.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
3 "http://mybatis.org/dtd/mybatis-3-config.dtd">
4
5 <configuration>
6 <properties>
7 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
8 <property name="url" value="jdbc:mysql://127.0.0.1:3306/gys?serverTimezone=UTC"/>
9 </properties>
10 <settings>
11 <setting name="defaultExecutorType" value="SIMPLE"/>
12 </settings>
13 <!--環境配置,連接配接的資料庫,這裡使用的是MySQL-->
14 <environments default="dev">
15 <environment id="dev">
16 <!--指定事務管理的類型,這裡簡單使用Java的JDBC的送出和復原設定-->
17 <transactionManager type="JDBC" />
18 <!--dataSource 指連接配接源配置,POOLED是JDBC連接配接對象的資料源連接配接池的實作-->
19 <dataSource type="POOLED">
20 <property name="driver" value="${driver}"></property>
21 <property name="url" value="${url}"></property>
22 <property name="username" value="root"></property>
23 <property name="password" value="gys"></property>
24 </dataSource>
25 </environment>
26 </environments>
27 <mappers>
28 <mapper resource="mapper/user.xml"></mapper>
29 </mappers>
30 </configuration>
user.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4 <mapper namespace="dao.IUserMapper">
5
6 <insert id="insertUser" parameterType="model.User">
7 insert into user
8 (name,age)
9 values
10 (#{name},#{age})
11 </insert>
12
13 </mapper>
入口方法main:
1 public static void main(String[] args) throws Exception {
2 SqlSessionFactory sqlSessionFactory1=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-mysql-config.xml"),"dev");
3 SqlSession sqlSession1= sqlSessionFactory1.openSession(true);
4 IUserMapper userMapper=sqlSession1.getMapper(IUserMapper.class);
5 User user=new User();
6 user.setAge(28);
7 user.setName("a");
8 int i=userMapper.insertUser(user);
9 System.out.println("受影響的行數"+i);
10 sqlSession1.close();
11 }
這個Executor的代碼離上面Demo執行代碼還有一段很長封裝,如果分析Executor,就要分析分析這段很長的封裝代碼;
這個源碼該怎麼開始才能讓人覺得水到渠成,順其自然呢?
算了,還是硬着頭皮一步一步來吧;
第一步:build過程中,如何擷取到 defaultExecutorType配置

第75行執行個體化一個XMLConfigBuilder對象,這是一個xml解析器。
第78行調用第91行的build方法,這個方法的參數是個Configuration對象,那麼parser.parse()方法傳回的一定是一個Configuration對象;
換句話說就是在parser.parse()中讀取的配置檔案,并且指派給configuratiion對象。
parser.parse()源碼:
parseConfiguration()源碼
1 private void parseConfiguration(XNode root) {
2 try {
3 propertiesElement(root.evalNode("properties"));
4 Properties settings = settingsAsProperties(root.evalNode("settings"));
5 loadCustomVfs(settings);
6 loadCustomLogImpl(settings);
7 typeAliasesElement(root.evalNode("typeAliases"));
8 pluginElement(root.evalNode("plugins"));
9 objectFactoryElement(root.evalNode("objectFactory"));
10 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11 reflectorFactoryElement(root.evalNode("reflectorFactory"));
12 //解析settings中的内容
13 settingsElement(settings);
14 environmentsElement(root.evalNode("environments"));
15 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
16 typeHandlerElement(root.evalNode("typeHandlers"));
17 mapperElement(root.evalNode("mappers"));
18 } catch (Exception e) {
19 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
20 }
21 }
根據方法名大概能推斷出處理的都是那些配置;直接看13行的代碼吧。
settingsElement()源碼:
1 private void settingsElement(Properties props) {
2 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
3 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
4 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
5 configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
6 configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
7 configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
8 configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
9 configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
10 configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
11 configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
12 configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
13 configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
14 configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
15 configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
16 configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
17 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
18 configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
19 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
20 configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
21 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
22 configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
23 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
24 configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
25 configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
26 configuration.setLogPrefix(props.getProperty("logPrefix"));
27 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
28 }
看看第11行代碼就是給configuration明确defaultExecutorType值的。
getProperty(key,defaultValue)有兩個參數,第一個參數是屬性的鍵值,根據這個鍵值擷取屬性值,如果擷取不到就用第二個參數作為預設值。
如果沒有配置 <setting name="defaultExecutorType" value="SIMPLE"/>,則用SIMPLE值。
說到這就來說說defaultExecutorType有哪些參數
SIMPLE:就是普通的執行器
REUSE: 執行器會重(讀:蟲)用預處理語句(PreparedStatements)(re+use=重複+使用)
BATCH 執行器将重(蟲)用語句并執行批量更新。
我一開始的看到官方的内容既不知道這三個什麼意思,也不知道那個字到底讀chong,還是讀zhong.
先改參數跑Demo看結果。
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultExecutorType" value="REUSE"/>
這兩個配置分别配置後,都可以正常的往資料庫中插入一條資料,并且傳回影響行數是1,隻有
<setting name="defaultExecutorType" value="BATCH"/>
這條配置傳回的資料是下面這個奇怪的東西;也沒有往資料庫中插入資料,也不抛異常。
受影響的行數-2147482646
1.SIMPLE和REUSE配置都能正常運作,那麼差別在哪?
2.BATCH配置傳回的奇怪的資料是什麼東西,為什麼不成功。
說了這麼久都還沒有說Executor是幹什麼的。
Executor代表執行器,有它來排程StatementdHandler,ParameterHandler,ResultHandler等來執行對應的SQL.
對于JDBC熟悉一點的人對上面幾個Handler名字應該有點熟悉。如果忘記了JDBC,看看這篇部落格:不了解jdbc,何談Mybatis的源碼解析?
Executor既然是總排程,那麼它應該是在MyBatis中Mapper的JDK動态代理的地方開始調用的。
如果不清楚這個動态代理,可以看看這篇部落格:從mybatis源碼看JDK動态代理
可以從Demo中的第四行代碼getMapper()方法往下追。
getMapper()的調用順序如下:
defaultSqlSession.getMapper()==》configuration.getMapper()==>MapperRegistry.getMapper()==>MapperProxyFactory.newInstance();
根據最後一張圖的46和47行代碼,結合JDK動态代理的調用規則,可以推斷MapperProxy中一定實作了 InvocationHandler 接口;并且實作了invoke方法。
1 @Override
2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
3 try {
4 //調用object中的方法
5 if (Object.class.equals(method.getDeclaringClass())) {
6 return method.invoke(this, args);
7 } else if (method.isDefault()) {
8 //調用接口中的預設方法(jdk9之後,接口可以有方法體)
9 if (privateLookupInMethod == null) {
10 return invokeDefaultMethodJava8(proxy, method, args);
11 } else {
12 return invokeDefaultMethodJava9(proxy, method, args);
13 }
14 }
15 } catch (Throwable t) {
16 throw ExceptionUtil.unwrapThrowable(t);
17 }
18 final MapperMethod mapperMethod = cachedMapperMethod(method);//将method包裝成MapperMethod對象
19 return mapperMethod.execute(sqlSession, args);
20 }
execute()源碼:
1 public Object execute(SqlSession sqlSession, Object[] args) {
2 Object result;
3 switch (command.getType()) {
4 case INSERT: {//sql是insert語句
5 Object param = method.convertArgsToSqlCommandParam(args);
6 result = rowCountResult(sqlSession.insert(command.getName(), param));
7 break;
8 }
9 case UPDATE: {//sql是update語句
10 Object param = method.convertArgsToSqlCommandParam(args);
11 result = rowCountResult(sqlSession.update(command.getName(), param));
12 break;
13 }
14 case DELETE: {//sql是delete語句
15 Object param = method.convertArgsToSqlCommandParam(args);
16 result = rowCountResult(sqlSession.delete(command.getName(), param));
17 break;
18 }
19 case SELECT://sql是select語句
20 if (method.returnsVoid() && method.hasResultHandler()) {
21 executeWithResultHandler(sqlSession, args);
22 result = null;
23 } else if (method.returnsMany()) {
24 result = executeForMany(sqlSession, args);
25 } else if (method.returnsMap()) {
26 result = executeForMap(sqlSession, args);
27 } else if (method.returnsCursor()) {
28 result = executeForCursor(sqlSession, args);
29 } else {
30 Object param = method.convertArgsToSqlCommandParam(args);
31 result = sqlSession.selectOne(command.getName(), param);
32 if (method.returnsOptional()
33 && (result == null || !method.getReturnType().equals(result.getClass()))) {
34 result = Optional.ofNullable(result);
35 }
36 }
37 break;
38 case FLUSH:
39 result = sqlSession.flushStatements();
40 break;
41 return result;
42 }
代碼加注釋應該能知道判斷本次sql的類型。我們Demo執行的是insert語句;是以我們檢視第六行的sqlsession.insert()
1 @Override
2 public int insert(String statement, Object parameter) {
3 return update(statement, parameter);
4 }
5
6 @Override
7 public int update(String statement, Object parameter) {
8 try {
9 dirty = true;
10 MappedStatement ms = configuration.getMappedStatement(statement);
//記住這個地方,等會還要回到這個地方;現在我們要去搞明白executor是從哪裡傳過來的,是何種執行器。
11 return executor.update(ms, wrapCollection(parameter));
12 } catch (Exception e) {
13 throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
14 } finally {
15 ErrorContext.instance().reset();
16 }
17 }
上面的insert方法調用的是update方法。
在update方法中終于看到了executor這個變量,到此終于看到了本篇的主角。
從目前的分析來看,還沒有看到executor是從什麼地方進行傳過來的。
executor是defaultSqlsession的一個屬性,
既是私有的、又是final修飾的,沒有辦法進行set指派,是以必然隻能在執行個體化的時候進行指派,上圖源碼第57行應驗了我們的觀點。
現在就是要找到DefaultSqlSession在什麼地方調用執行個體化的,就能知道執行器是怎麼傳的了。
捋一下思路,sqlSession是一次與資料庫會話的玩意,相當于Jdbc中的connection。
這時候我們翻到Demo裡面的代碼看到第三行代碼:SqlSession sqlSession1= sqlSessionFactory1.openSession(true);方法。
從字面就能了解sqlSessionFactory工廠建立一個會話對象。繼續追蹤openSession源碼。
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
openSessionFromDataSource()傳了建構configuration對象時的執行器;getDefaultExecutorType()擷取配置的執行器。
繼續追蹤openSessionFromDataSource()源碼;去除多餘的代碼
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//擷取執行器,因為configuration存的是枚舉,需要根據枚舉轉換成實際的執行器對象
final Executor executor = configuration.newExecutor(tx, execType);
//執行個體化defaultSqlSession;本次資料庫會話的執行器确定下來了。
return new DefaultSqlSession(configuration, executor, autoCommit);
}
确定了執行器之後,繼續回到上面 executor.update(ms, wrapCollection(parameter));方法調用的地方。
executor是一個接口變量,他一定指向某個實作類。
搞清楚了executor的來源。現在這個地方的update就應該知道是哪個執行器裡買的方法了。
根據配置隻會從SimpleExecutor,ReuseExecutor,BatchExecutor三個類中去執行。
現在可以把這三個類單獨拿出來分析了。
SimpleExecutor.java
這個裡面沒有找到update(),可能是繼承的原因,在BaseExecutor裡面去找。
調用的是doUpdate()方法
它是一個抽象方法,點選左邊的繼承按鈕。
繼續回到SimpleExecutor中的doUpdate()方法。
1 @Override
2 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
3 Statement stmt = null;
4 try {
5 Configuration configuration = ms.getConfiguration();
6 //StatementHandler後面再說吧
7 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
8 //擷取Statement具體對象
9 stmt = prepareStatement(handler, ms.getStatementLog());
10 return handler.update(stmt);
11 } finally {
12 closeStatement(stmt);
13 }
14 }
還是繼續追蹤prepareStatement()源碼
1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2 Statement stmt;
3 Connection connection = getConnection(statementLog);
4 //對sql進行預編譯,并傳回預編譯對象。
5 stmt = handler.prepare(connection, transaction.getTimeout());
6 //對sql中的?參數進行設定
7 handler.parameterize(stmt);
8 return stmt;
9 }
這裡面的getConnection(),preparse(),parameterize()方法的執行還有很長的調用鍊,後面就是參數如何轉換的問題,牽扯的TypeHandler子產品了,後面在單獨開辟一篇分析吧。
至此SimpleExecutor執行器分析完了;
繼續分析ReuseExecutor.java
用和SimpleExecutor同樣的方法找到prepareStatement()方法
1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2 Statement stmt;
3 BoundSql boundSql = handler.getBoundSql();
4 //擷取sql語句
5 String sql = boundSql.getSql();
6 //查詢這條sql是否預編譯過,有的話直接擷取預編譯對象
7 if (hasStatementFor(sql)) {
8 stmt = getStatement(sql);
9 applyTransactionTimeout(stmt);
10 } else {
11 //第一次預編譯,直接建立一個預編譯對象
12 Connection connection = getConnection(statementLog);
13 stmt = handler.prepare(connection, transaction.getTimeout());
14 //緩存預編譯對象
15 putStatement(sql, stmt);
16 }
17 //sql中的參數替換
18 handler.parameterize(stmt);
19 return stmt;
20 }
ReuseExecutor的prepareStatement方法比SimExecutor的prepareStatement()方法多了一個sql的預編譯對象的儲存。
至此終于明白了為什麼 REUSE配置叫 重(蟲)用sql語句了;re(重複)+use(使用)=reuse。
BatchExecutor分析。
//代碼很長大,但是隻要看最後兩行代碼就行了。
@Override
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);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
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);
} 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;
}
首先這個方法本身就很奇怪;不管執行結果如何,直接傳回 BATCH_UPDATE_RETURN_VALUE ;
我們看看BATCH_UPDATE_RETURN_VALUE 是什麼東西。
直接就是int的最小值加1002;不明白為什麼要傳回這麼個特定的值;為什麼不直接傳回1,2,張三,阿毛、阿狗?
現在終于知道上面的insert為什麼在BATCH配置的情況下,傳回下面這個截圖内容了
handler.batch(stmt)源碼
直接addBatch()就結束了,沒有向資料庫發送執行操作的代碼,比如ps.
executeBatch()方法。
現在也終于知道為什麼資料庫裡面一直都沒有新增資料了。
可是為什麼要這麼設計呢?是代碼沒寫完麼?
1.SimpleExecutor是對jdbc中PreParement的封裝
2.ReuseExecutor是對Jdbc中PreParement封裝的基礎上進行緩存,以達到sql活預編譯對象的重複使用
3.希望知道答案的博友們告知一下,為什麼Batch執行器不寫操作資料庫的代碼,如果不寫,那要這個執行器還有什麼用呢?