statement执行insert into语句_【图文并茂】源码解析MyBatis ShardingJdbc SQL语句执行流程详解...
源碼分析Mybatis系列目錄:
1、源碼分析Mybatis MapperProxy初始化【圖文并茂】
2、源碼分析Mybatis MappedStatement的創(chuàng)建流程
3、【圖文并茂】Mybatis執(zhí)行SQL的4大基礎(chǔ)組件詳解
如果圖不清晰的話,可以查看CSDN博客鏈接:https://blog.csdn.net/prestigeding/article/details/90647674
本文將詳細(xì)介紹Mybatis SQL語句執(zhí)行的全流程,本文與上篇具有一定的關(guān)聯(lián)性,建議先閱讀該系列中的前面3篇文章,重點掌握Mybatis Mapper類的初始化過程,因為在Mybatis中,Mapper是執(zhí)行SQL語句的入口,類似下面這段代碼:
1@Service2public?UserService?implements?IUserService?{
3?????@Autowired
4????private?UserMapper?userMapper;
5????public?User?findUser(Integer?id)?{
6????????return?userMapper.find(id);
7????}
8}
開始進(jìn)入本文的主題,以源碼為手段,分析Mybatis執(zhí)行SQL語句的流行,并且使用了數(shù)據(jù)庫分庫分表中間件sharding-jdbc,其版本為sharding-jdbc1.4.1。
為了方便大家對本文的源碼分析,先給出Mybatis層面核心類的方法調(diào)用序列圖。
SQL執(zhí)行序列圖
源碼解析SQL執(zhí)行流程
接下來從從源碼的角度對其進(jìn)行剖析。
溫馨提示:在本文的末尾,還會給出一張詳細(xì)的Mybatis Shardingjdbc語句執(zhí)行流程圖。(請勿錯過哦)。
2.1 MapperProxy#invoker
1public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{2????if?(Object.class.equals(method.getDeclaringClass()))?{
3??????try?{
4????????return?method.invoke(this,?args);
5??????}?catch?(Throwable?t)?{
6????????throw?ExceptionUtil.unwrapThrowable(t);
7??????}
8????}
9????final?MapperMethod?mapperMethod?=?cachedMapperMethod(method);???//?@1
10????return?mapperMethod.execute(sqlSession,?args);?????????????????????????????????????//?@2
11??}
代碼@1:創(chuàng)建并緩存MapperMethod對象。
代碼@2:調(diào)用MapperMethod對象的execute方法,即mapperInterface中定義的每一個方法最終會對應(yīng)一個MapperMethod。
2.2 MapperMethod#execute
1public?Object?execute(SqlSession?sqlSession,?Object[]?args)?{2????Object?result;
3????if?(SqlCommandType.INSERT?==?command.getType())?{?
4??????Object?param?=?method.convertArgsToSqlCommandParam(args);
5??????result?=?rowCountResult(sqlSession.insert(command.getName(),?param));
6????}?else?if?(SqlCommandType.UPDATE?==?command.getType())?{
7??????Object?param?=?method.convertArgsToSqlCommandParam(args);
8??????result?=?rowCountResult(sqlSession.update(command.getName(),?param));
9????}?else?if?(SqlCommandType.DELETE?==?command.getType())?{
10??????Object?param?=?method.convertArgsToSqlCommandParam(args);
11??????result?=?rowCountResult(sqlSession.delete(command.getName(),?param));
12????}?else?if?(SqlCommandType.SELECT?==?command.getType())?{
13??????if?(method.returnsVoid()?&&?method.hasResultHandler())?{
14????????executeWithResultHandler(sqlSession,?args);
15????????result?=?null;
16??????}?else?if?(method.returnsMany())?{
17????????result?=?executeForMany(sqlSession,?args);
18??????}?else?if?(method.returnsMap())?{
19????????result?=?executeForMap(sqlSession,?args);
20??????}?else?{
21????????Object?param?=?method.convertArgsToSqlCommandParam(args);
22????????result?=?sqlSession.selectOne(command.getName(),?param);
23??????}
24????}?else?{
25??????throw?new?BindingException("Unknown?execution?method?for:?"?+?command.getName());
26????}
27????if?(result?==?null?&&?method.getReturnType().isPrimitive()?&&?!method.returnsVoid())?{
28??????throw?new?BindingException("Mapper?method?'"?+?command.getName()?
29??????????+?"?attempted?to?return?null?from?a?method?with?a?primitive?return?type?("?+?method.getReturnType()?+?").");
30????}
31????return?result;
32??}
該方法主要是根據(jù)SQL類型,insert、update、select等操作,執(zhí)行對應(yīng)的邏輯,本文我們以查詢語句,進(jìn)行跟蹤,進(jìn)入executeForMany(sqlSession, args)方法。
2.3 MapperMethod#executeForMany
1private??Object?executeForMany(SqlSession?sqlSession,?Object[]?args)?{ 2????List?result; 3????Object?param?=?method.convertArgsToSqlCommandParam(args); 4????if?(method.hasRowBounds())?{ 5??????RowBounds?rowBounds?=?method.extractRowBounds(args); 6??????result?=?sqlSession.selectList(command.getName(),?param,?rowBounds); 7????}?else?{ 8??????result?=?sqlSession.selectList(command.getName(),?param); 9????}10????//?issue?#510?Collections?&?arrays?support11????if?(!method.getReturnType().isAssignableFrom(result.getClass()))?{12??????if?(method.getReturnType().isArray())?{13????????return?convertToArray(result);14??????}?else?{15????????return?convertToDeclaredCollection(sqlSession.getConfiguration(),?result);16??????}17????}18????return?result;19??}該方法也比較簡單,最終通過SqlSession調(diào)用selectList方法。
2.4 DefaultSqlSession#selectList
1public??List?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{ 2????try?{ 3??????MappedStatement?ms?=?configuration.getMappedStatement(statement);???//?@1 4??????List?result?=?executor.query(ms,?wrapCollection(parameter),?rowBounds,?Executor.NO_RESULT_HANDLER);???//?@2 5??????return?result; 6????}?catch?(Exception?e)?{ 7??????throw?ExceptionFactory.wrapException("Error?querying?database.??Cause:?"?+?e,?e); 8????}?finally?{ 9??????ErrorContext.instance().reset();10????}11??}代碼@1:根據(jù)資源名稱獲取對應(yīng)的MappedStatement對象,此時的statement為資源名稱,例如com.demo.UserMapper.findUser。至于MappedStatement對象的生成在上一節(jié)初始化時已詳細(xì)介紹過,此處不再重復(fù)介紹。
代碼@2:調(diào)用Executor的query方法。這里說明一下,其實一開始會進(jìn)入到CachingExecutor#query方法,由于CachingExecutor的Executor delegate屬性默認(rèn)是SimpleExecutor,故最終還是會進(jìn)入到SimpleExecutor#query中。
接下來我們進(jìn)入到SimpleExecutor的父類BaseExecutor的query方法中。
2.5 BaseExecutor#query
1public??List?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{???//?@1 2????ErrorContext.instance().resource(ms.getResource()).activity("executing?a?query").object(ms.getId()); 3????if?(closed)?throw?new?ExecutorException("Executor?was?closed."); 4????if?(queryStack?==?0?&&?ms.isFlushCacheRequired())?{ 5??????clearLocalCache(); 6????} 7????List?list; 8????try?{ 9??????queryStack++;10??????list?=?resultHandler?==?null???(List)?localCache.getObject(key)?:?null;????????????????????????????????????????????//?@211??????if?(list?!=?null)?{12????????handleLocallyCachedOutputParameters(ms,?key,?parameter,?boundSql);13??????}?else?{14????????list?=?queryFromDatabase(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);???????????????????//?@315??????}16????}?finally?{17??????queryStack--;18????}19????if?(queryStack?==?0)?{20??????for?(DeferredLoad?deferredLoad?:?deferredLoads)?{21????????deferredLoad.load();22??????}23??????deferredLoads.clear();?//?issue?#60124??????if?(configuration.getLocalCacheScope()?==?LocalCacheScope.STATEMENT)?{?????????????????????????//?@425????????clearLocalCache();?//?issue?#48226??????}27????}28????return?list;29??}代碼@1:首先介紹一下該方法的入?yún)?#xff0c;這些類都是Mybatis的重要類:
MappedStatement ms
映射語句,一個MappedStatemnet對象代表一個Mapper中的一個方法,是映射的最基本對象。Object parameter
SQL語句的參數(shù)列表。RowBounds rowBounds
行邊界對象,其實就是分頁參數(shù)limit與size。ResultHandler resultHandler
結(jié)果處理Handler。CacheKey key
Mybatis緩存KeyBoundSql boundSql
SQL與參數(shù)綁定信息,從該對象可以獲取在映射文件中的SQL語句。
代碼@2:首先從緩存中獲取,Mybatis支持一級緩存(SqlSession)與二級緩存(多個SqlSession共享)。
代碼@3:從數(shù)據(jù)庫查詢結(jié)果,然后進(jìn)入到doQuery方法,執(zhí)行真正的查詢動作。
代碼@4:如果一級緩存是語句級別的,則語句執(zhí)行完畢后,刪除緩存。
2.6 SimpleExecutor#doQuery
1public??List?doQuery(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?BoundSql?boundSql)?throws?SQLException?{ 2????Statement?stmt?=?null; 3????try?{ 4??????Configuration?configuration?=?ms.getConfiguration(); 5??????StatementHandler?handler?=?configuration.newStatementHandler(wrapper,?ms,?parameter,?rowBounds,?resultHandler,?boundSql);???//?@1 6??????stmt?=?prepareStatement(handler,?ms.getStatementLog());???????????????????????????????????????????????????????????????????????????????????????????????????????????????????//?@2 7??????return?handler.query(stmt,?resultHandler);????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????//?@3 8????}?finally?{ 9??????closeStatement(stmt);10????}11??}代碼@1:創(chuàng)建StatementHandler,這里會加入Mybatis的插件擴(kuò)展機(jī)制(將在下篇詳細(xì)介紹),如圖所示:
在這里插入圖片描述代碼@2:創(chuàng)建Statement對象,注意,這里就是JDBC協(xié)議的java.sql.Statement對象了。
代碼@3:使用Statment對象執(zhí)行SQL語句。
接下來詳細(xì)介紹Statement對象的創(chuàng)建過程與執(zhí)行過程,即分布詳細(xì)跟蹤代碼@2與代碼@3。
Statement對象創(chuàng)建流程
3.1 java.sql.Connection對象創(chuàng)建
SimpleExecutor#prepareStatement
1private?Statement?prepareStatement(StatementHandler?handler,?Log?statementLog)?throws?SQLException?{2????Statement?stmt;
3????Connection?connection?=?getConnection(statementLog);??//?@1
4????stmt?=?handler.prepare(connection);??????????????????????????????????//?@2
5????handler.parameterize(stmt);???????????????????????????????????????????????//?@3
6????return?stmt;
7}
創(chuàng)建Statement對象,分成三步:
代碼@1:創(chuàng)建java.sql.Connection對象。
代碼@2:使用Connection對象創(chuàng)建Statment對象。
代碼@3:對Statement進(jìn)行額外處理,特別是PrepareStatement的參數(shù)設(shè)置(ParameterHandler)。
SimpleExecutor#getConnection
getConnection方法,根據(jù)上面流程圖所示,先是進(jìn)入到SpringManagedTransaction,再通過spring-jdbc框架,利用DataSourceUtils獲取連接,其代碼如下:
1org.mybatis.spring.transaction.SpringManagedTransaction#doGetConnection 2public?static?Connection?doGetConnection(DataSource?dataSource)?throws?SQLException?{??3????????Assert.notNull(dataSource,?"No?DataSource?specified");
4????????ConnectionHolder?conHolder?=?(ConnectionHolder)?TransactionSynchronizationManager.getResource(dataSource);?
5????????if?(conHolder?!=?null?&&?(conHolder.hasConnection()?||?conHolder.isSynchronizedWithTransaction()))?{
6????????????conHolder.requested();
7????????????if?(!conHolder.hasConnection())?{
8????????????????conHolder.setConnection(dataSource.getConnection());
9????????????}
10????????????return?conHolder.getConnection();
11????????}
12????????//?Else?we?either?got?no?holder?or?an?empty?thread-bound?holder?here.
13
14????????logger.debug("Fetching?JDBC?Connection?from?DataSource");
15????????Connection?con?=?dataSource.getConnection();??????//?@1
16
17????????//?這里省略與事務(wù)處理相關(guān)的代碼
18????????return?con;
19????}
代碼@1:通過DataSource獲取connection,那此處的DataSource是“誰”呢?看一下我們工程的配置:
故最終dataSouce.getConnection獲取的連接,是從SpringShardingDataSource中獲取連接。
1com.dangdang.ddframe.rdb.sharding.jdbc.ShardingDataSource#getConnection2public?ShardingConnection?getConnection()?throws?SQLException?{3????????MetricsContext.init(shardingProperties);
4????????return?new?ShardingConnection(shardingContext);
5}
返回的結(jié)果如下:
備注:這里只是返回了一個ShardingConnection對象,該對象包含了分庫分表上下文,但此時并沒有執(zhí)行具體的分庫操作(切換數(shù)據(jù)源)。
Connection的獲取流程清楚后,我們繼續(xù)來看一下Statemnet對象的創(chuàng)建。
3.2 java.sql.Statement對象創(chuàng)建
1stmt?=?prepareStatement(handler,?ms.getStatementLog());????????????上面語句的調(diào)用鏈:RoutingStatementHandler -》BaseStatementHand
BaseStatementHandler#prepare
3public?Statement?prepare(Connection?connection)?throws?SQLException?{4????ErrorContext.instance().sql(boundSql.getSql());
5????Statement?statement?=?null;
6????try?{
7??????statement?=?instantiateStatement(connection);????//?@1
8??????setStatementTimeout(statement);?????????????????????????//?@2
9??????setFetchSize(statement);??????????????????????????????????????//?@3
10??????return?statement;
11????}?catch?(SQLException?e)?{
12??????closeStatement(statement);
13??????throw?e;
14????}?catch?(Exception?e)?{
15??????closeStatement(statement);
16??????throw?new?ExecutorException("Error?preparing?statement.??Cause:?"?+?e,?e);
17????}
18??}
代碼@1:根據(jù)Connection對象(本文中是ShardingConnection)來創(chuàng)建Statement對象,其默認(rèn)實現(xiàn)類:PreparedStatementHandler#instantiateStatement方法。
代碼@2:為Statement設(shè)置超時時間。
代碼@3:設(shè)置fetchSize。
1PreparedStatementHandler#instantiateStatement 2protected?Statement?instantiateStatement(Connection?connection)?throws?SQLException?{3????String?sql?=?boundSql.getSql();
4????if?(mappedStatement.getKeyGenerator()?instanceof?Jdbc3KeyGenerator)?{
5??????String[]?keyColumnNames?=?mappedStatement.getKeyColumns();
6??????if?(keyColumnNames?==?null)?{
7????????return?connection.prepareStatement(sql,?PreparedStatement.RETURN_GENERATED_KEYS);
8??????}?else?{
9????????return?connection.prepareStatement(sql,?keyColumnNames);
10??????}
11????}?else?if?(mappedStatement.getResultSetType()?!=?null)?{
12??????return?connection.prepareStatement(sql,?mappedStatement.getResultSetType().getValue(),?ResultSet.CONCUR_READ_ONLY);
13????}?else?{
14??????return?connection.prepareStatement(sql);
15????}
16??}
其實Statement對象的創(chuàng)建,就比較簡單了,既然Connection是ShardingConnection,那就看一下其對應(yīng)的prepareStatement方法即可。
ShardingConnection#prepareStatement
1 3public?PreparedStatement?prepareStatement(final?String?sql)?throws?SQLException?{???//?sql,為配置在mybatis?xml文件中的sql語句4????????return?new?ShardingPreparedStatement(this,?sql);
5}
6ShardingPreparedStatement(final?ShardingConnection?shardingConnection,?
7????????????final?String?sql,?final?int?resultSetType,?final?int?resultSetConcurrency,?final?int?resultSetHoldability)?{
8????????super(shardingConnection,?resultSetType,?resultSetConcurrency,?resultSetHoldability);
9????????preparedSQLRouter?=?shardingConnection.getShardingContext().getSqlRouteEngine().prepareSQL(sql);
10}
在構(gòu)建ShardingPreparedStatement對象的時候,會根據(jù)SQL語句創(chuàng)建解析SQL路由的解析器對象,但此時并不會執(zhí)行相關(guān)的路由計算,PreparedStatement對象創(chuàng)建完成后,就開始進(jìn)入SQL執(zhí)行流程中。
SQL執(zhí)行流程
接下來我們繼續(xù)看SimpleExecutor#doQuery方法的第3步,執(zhí)行SQL語句:
1handler.query(stmt,?resultHandler)。首先會進(jìn)入RoutingStatementHandler這個類中,進(jìn)行Mybatis層面的路由(主要是根據(jù)Statement類型)
然后進(jìn)入到PreparedStatementHandler#query中。
PreparedStatementHandler#query
3public??List?query(Statement?statement,?ResultHandler?resultHandler)?throws?SQLException?{4????PreparedStatement?ps?=?(PreparedStatement)?statement;5????ps.execute();??//?@16????return?resultSetHandler.?handleResultSets(ps);??//?@27}
代碼@1:調(diào)用PreparedStatement的execute方法,由于本例是使用了Sharding-jdbc分庫分表,此時調(diào)用的具體實現(xiàn)為:ShardingPreparedStatement。
代碼@2:處理結(jié)果。
我們接下來分別來跟進(jìn)execute與結(jié)果處理方法。
ShardingPreparedStatement#execute
2public?boolean?execute()?throws?SQLException?{3????try?{
4????????return?new?PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(),?routeSQL()).execute();?//?@1
5????}?finally?{
6????????clearRouteContext();
7????}
8}
這里奧妙無窮,其關(guān)鍵點如下:
1)創(chuàng)造PreparedStatementExecutor對象,其兩個核心參數(shù):
ExecutorEngine executorEngine:shardingjdbc執(zhí)行引擎。
Collection< PreparedStatementExecutorWrapper> preparedStatemenWrappers
一個集合,每一個集合是PreparedStatement的包裝類,這個集合如何而來?
2)preparedStatemenWrappers是通過routeSQL方法產(chǎn)生的。
3)最終調(diào)用PreparedStatementExecutor方法的execute來執(zhí)行。
接下來分別看一下routeSQL與execute方法。
ShardingPreparedStatement#routeSQL
3private?List?routeSQL()?throws?SQLException?{4????????List?result?=?new?ArrayList<>(); 5????????SQLRouteResult?sqlRouteResult?=?preparedSQLRouter.route(getParameters());???//?@1 6????????MergeContext?mergeContext?=?sqlRouteResult.getMergeContext();?????????????????????? 7????????setMergeContext(mergeContext); 8????????setGeneratedKeyContext(sqlRouteResult.getGeneratedKeyContext()); 9????????for?(SQLExecutionUnit?each?:?sqlRouteResult.getExecutionUnits())?{??????????????????????//?@2??????????10????????????PreparedStatement?preparedStatement?=?(PreparedStatement)?getStatement(getShardingConnection().getConnection(each.getDataSource(),?sqlRouteResult.getSqlStatementType()),?each.getSql());?????//?@311????????????replayMethodsInvocation(preparedStatement);12????????????getParameters().replayMethodsInvocation(preparedStatement);13????????????result.add(wrap(preparedStatement,?each));14????????}15????????return?result;16}
代碼@1:根據(jù)SQL參數(shù)進(jìn)行路由計算,本文暫不關(guān)注其具體實現(xiàn)細(xì)節(jié),這些將在具體分析Sharding-jdbc時具體詳解,在這里就直觀看一下其結(jié)果:
代碼@2、@3:對分庫分表的結(jié)果進(jìn)行遍歷,然后使用底層Datasource來創(chuàng)建Connection,創(chuàng)建PreparedStatement 對象。
routeSQL就暫時講到這,從這里我們得知,會在這里根據(jù)路由結(jié)果,使用底層的具體數(shù)據(jù)源創(chuàng)建對應(yīng)的Connection與PreparedStatement 對象。
PreparedStatementExecutor#execute
1 3public?boolean?execute()?{4????Context?context?=?MetricsContext.start("ShardingPreparedStatement-execute");
5????eventPostman.postExecutionEvents();
6????final?boolean?isExceptionThrown?=?ExecutorExceptionHandler.isExceptionThrown();
7????final?Map?dataMap?=?ExecutorDataMap.getDataMap(); 8????try?{ 9????????if?(1?==?preparedStatementExecutorWrappers.size())?{?????//?@110????????????PreparedStatementExecutorWrapper?preparedStatementExecutorWrapper?=?preparedStatementExecutorWrappers.iterator().next();11????????????return?executeInternal(preparedStatementExecutorWrapper,?isExceptionThrown,?dataMap);12????????}13????????List?result?=?executorEngine.execute(preparedStatementExecutorWrappers,?new?ExecuteUnit()?{????//?@21415????????????@Override16????????????public?Boolean?execute(final?PreparedStatementExecutorWrapper?input)?throws?Exception?{17????????????????synchronized?(input.getPreparedStatement().getConnection())?{18????????????????????return?executeInternal(input,?isExceptionThrown,?dataMap);19????????????????}20????????????}21????????});22????????return?(null?==?result?||?result.isEmpty())???false?:?result.get(0);23????}?finally?{24????????MetricsContext.stop(context);25????}26?}
代碼@1:如果計算出來的路由信息為1個,則同步執(zhí)行。
代碼@2:如果計算出來的路由信息有多個,則使用線程池異步執(zhí)行。
那還有一個問題,通過PreparedStatement#execute方法執(zhí)行后,如何返回結(jié)果呢?特別是異步執(zhí)行的。
在上文其實已經(jīng)談到:
DefaultResultSetHandler#handleResultSets
1 3public?List?handleResultSets(Statement?stmt)?throws?SQLException?{4????ErrorContext.instance().activity("handling?results").object(mappedStatement.getId());
5
6????final?List?multipleResults?=?new?ArrayList(); 7 8????int?resultSetCount?=?0; 9????ResultSetWrapper?rsw?=?getFirstResultSet(stmt);?????????//?@110????//省略部分代碼,完整代碼可以查看DefaultResultSetHandler方法。11????return?collapseSingleResultList(multipleResults);12??}1314private?ResultSetWrapper?getFirstResultSet(Statement?stmt)?throws?SQLException?{15????ResultSet?rs?=?stmt.getResultSet();??????????????//?@216????while?(rs?==?null)?{17??????//?move?forward?to?get?the?first?resultset?in?case?the?driver18??????//?doesn't?return?the?resultset?as?the?first?result?(HSQLDB?2.1)19??????if?(stmt.getMoreResults())?{20????????rs?=?stmt.getResultSet();21??????}?else?{22????????if?(stmt.getUpdateCount()?==?-1)?{23??????????//?no?more?results.?Must?be?no?resultset24??????????break;25????????}26??????}27????}28????return?rs?!=?null???new?ResultSetWrapper(rs,?configuration)?:?null;29??}
我們看一下其關(guān)鍵代碼如下:
代碼@1:調(diào)用Statement#getResultSet()方法,如果使用shardingJdbc,則會調(diào)用ShardingStatement#getResultSet(),并會處理分庫分表結(jié)果集的合并,在這里就不詳細(xì)進(jìn)行介紹,該部分會在shardingjdbc專欄詳細(xì)分析。
代碼@2:jdbc statement中獲取結(jié)果集的通用寫法,這里也不過多的介紹。
mybatis shardingjdbc SQL執(zhí)行流程就介紹到這里了,為了方便大家對上述流程的理解,最后給出SQL執(zhí)行的流程圖:
Mybatis Sharding-Jdbc的SQL執(zhí)行流程就介紹到這里了,從圖中也能清晰看到Mybatis的拆件機(jī)制,將在下文詳細(xì)介紹。查看更多文章請關(guān)注微信公眾號:
一波廣告來襲,作者新書《RocketMQ技術(shù)內(nèi)幕》已出版上市:
《RocketMQ技術(shù)內(nèi)幕》已出版上市,目前可在主流購物平臺(京東、天貓等)購買,本書從源碼角度深度分析了RocketMQ NameServer、消息發(fā)送、消息存儲、消息消費、消息過濾、主從同步HA、事務(wù)消息;在實戰(zhàn)篇重點介紹了RocketMQ運維管理界面與當(dāng)前支持的39個運維命令;并在附錄部分羅列了RocketMQ幾乎所有的配置參數(shù)。本書得到了RocketMQ創(chuàng)始人、阿里巴巴Messaging開源技術(shù)負(fù)責(zé)人、Linux OpenMessaging 主席的高度認(rèn)可并作序推薦。目前是國內(nèi)第一本成體系剖析RocketMQ的書籍。
總結(jié)
以上是生活随笔為你收集整理的statement执行insert into语句_【图文并茂】源码解析MyBatis ShardingJdbc SQL语句执行流程详解...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【ES9(2018)】Object Re
- 下一篇: 数字系统设计学习之VHDL输入设计