爲了便於源碼分析,仍是先來一個MyBatis的Demo吧html
mybatis-mysql-config.xmljava
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.xmlmysql
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:sql
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,就要分析分析這段很長的封裝代碼;數據庫
這個源碼該怎麼開始才能讓人以爲水到渠成,順其天然呢?緩存
算了,仍是硬着頭皮一步一步來吧;session
第一步:build過程當中,如何獲取到 defaultExecutorType配置mybatis
第75行實例化一個XMLConfigBuilder對象,這是一個xml解析器。app
第78行調用第91行的build方法,這個方法的參數是個Configuration對象,那麼parser.parse()方法返回的必定是一個Configuration對象;ide
換句話說就是在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執行器不寫操做數據庫的代碼,若是不寫,那要這個執行器還有什麼用呢?