很多朋友对于收吃灰:万字讲解Executor处理流程,依托源码,面向实践和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
在讲解缓存的过程中,我提到了一个问题
一级、二级缓存都是在Executor中进行的。
不过由于篇幅原因,我只展示了BasicExecutor的相关源码操作。剩下的Executor没有完全介绍。这里与上一篇文章有联系。你可以先阅读上一篇文章,然后再阅读本书。希望对大家有帮助
好了,废话不多说,我们一起来看看
SimpleExecutor
org.apache.ibatis.executor.SimpleExecutor:继承BaseExecutor抽象类,简单Executor实现类(默认使用)
对数据库的每一个操作都会创建一个对应的Statement对象。执行完成后,关闭Statement对象的代码如下:
公共类SimpleExecutor扩展BaseExecutor {public SimpleExecutor(配置配置,事务事务){super(配置,事务);}@Overridepublic int doUpdate(MappedStatement ms,对象参数)抛出SQLException {Statement stmt=null; try {配置配置=ms。 getConfiguration();//创建StatementHandler对象StatementHandler handler=configuration.newStatementHandler(this, ms,parameter, RowBounds.DEFAULT, null, null);//初始化Statement对象stmt=prepareStatement(handler, ms.getStatementLog());//通过StatementHandler执行写操作return handler.update(stmt);} finally { //关闭Statement对象closeStatement(stmt);}}@Overridepublic E ListE doQuery(MappedStatement ms, Object argument, RowBounds rowBounds, ResultHandler resultHandler,BoundSqlboundSql ) throws SQLException {Statement stmt=null;try {Configuration configuration=ms.getConfiguration();//创建StatementHandler对象StatementHandler handler=configuration.newStatementHandler(wrapper, ms,parameter, rowBounds, resultHandler,boundSql);//初始化Statement对象stmt=prepareStatement(handler, ms.getStatementLog());//通过StatementHandler执行读操作return handler.query(stmt, resultHandler);} finally {//关闭Statement对象closeStatement(stmt);}}@Overrideprotected E CursorE doQueryCursor( MappedStatement ms,对象参数,RowBounds rowBounds,BoundSqlboundSql)抛出SQLException {配置配置=ms.getConfiguration();StatementHandler 处理程序=配置.newStatementHandler(wrapper,ms,参数,rowBounds,null,boundSql);Statement stmt=prepareStatement(handler , ms.getStatementLog());CursorE 光标=handler.queryCursor(stmt);stmt.closeOnCompletion();返回光标;}@Overridepublic ListBatchResult doFlushStatements(boolean isRollback) {return Collections.emptyList();}private Statement prepareStatement(StatementHandler handler, Log statementsLog) throws SQLException {Statement stmt;//获取Connection对象。如果开启了Debug模式,则创建一个代理对象Connection connection=getConnection(statementLog);//创建Statement或PrepareStatement对象stmt=handler.prepare(connection, transaction.getTimeout());//设置SQL语句上的参数在Statement中,例如PrepareStatement的? placeholder handler.parameterize(stmt);return stmt;}} 我们看这几个方法的实现,其中的步骤几乎是一样的
获取Configuration全局配置对象,并通过上面全局配置对象的newStatementHandler方法创建RoutingStatementHandler对象。它采用装饰器模式,根据配置的StatementType创建对应的对象。默认值是PreparedStatementHandler 对象。进入BaseStatementHandler的构造方法,你会发现几个重要的步骤,后面会讲到,然后使用插件链来应用对象。方法如下: //Configuration.java
公共StatementHandler newStatementHandler(Executor执行器,MappedStatementmappedStatement,对象parameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql){
/* * 创建RoutingStatementHandler路由对象
* 根据StatementType创建对应类型的Statement对象,默认为PREPARED
* 所有执行的方法都会被路由到该对象*/
StatementHandler statementsHandler=new RoutingStatementHandler(executor,mappedStatement,parameterObject,rowBounds,resultHandler,boundSql);
//将Configuration全局配置中的所有插件应用到StatementHandler中
statementsHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);
返回语句处理程序; }
调用prepareStatement方法初始化Statement对象,并从事务中获取Connection数据库连接。如果开启了Debug模式,则会为Connection创建一个动态代理对象的实例,用于打印Debug日志。通过上面步骤2 中创建的StatementHandler 对象创建一个。 Statement对象(默认是PrepareStatement),也会做一些准备工作。例如:如果配置了KeyGenerator(设置主键),则会设置返回对应的自增键。稍后我们将讨论在Statement对象中设置SQL参数。比如PrepareStatement的占位符,实际上是通过DefaultParameterHandler设置占位符参数,通过StatementHandler对Statement进行数据库操作。如果是查询操作,则通过DefaultResultSetHandler进行参数映射(很复杂,后面会一步步分析)
ReuseExecutor
org.apache.ibatis.executor.ReuseExecutor:继承BaseExecutor抽象类和可复用的Executor实现类
对数据库的每次操作,首先从当前会话的缓存中获取对应的Statement对象。如果不存在,则会创建它。一旦创建,它将被放置在缓存中。数据库操作完成后,Statement对象不会关闭。其他和SimpleExecutor是一致的。我们看一下它的prepareStatement方法:
私有语句prepareStatement(StatementHandler处理程序,日志statementLog)抛出SQLException {Statement stmt; BoundSql BoundSql=handler.getBoundSql();字符串sql=boundSql.getSql(); /* * 根据需要执行的SQL语句判断是否有对应的Statement。并且连接没有关闭*/if (hasStatementFor(sql)) { //从缓存中获取Statement对象stmt=getStatement(sql); //重置事务超时applyTransactionTimeout(stmt); } else { //获取Connection对象Connection connection=getConnection(statementLog); //初始化Statement对象stmt=handler.prepare(connection, transaction.getTimeout()); //将Statement添加到缓存中,键值为当前执行的SQL语句putStatement(sql, stmt) ; } //在Statement中设置SQL语句上的参数,比如PrepareStatement的?占位符处理程序.parameterize(stmt); return stmt;} 在创建Statement对象之前,会根据本次查询的SQL从本地MapString中检索它。 StatementstatementMap获取对应的Statement对象
如果缓存命中并且对象的连接未关闭,则重置当前事务的超时。如果缓存未命中,则执行与SimpleExecutor中prepareStatement方法相同的逻辑,创建Statement对象并放入statementMap缓存中
BatchExecutor
org. apache.ibatis.executor.BatchExecutor:继承BaseExecutor抽象类,支持批量执行的Executor实现类。
当我们执行数据库更新操作时,可以通过Statement的addBatch()方法将数据库操作添加到批处理中,并等待Statement的executeBatch()方法被调用来进行批处理。 BatchExecutor维护了多个Statement对象,一个对象对应一个SQL(sql和MappedStatement对象是相等的),每个Statement对象对应多个数据库操作(同一个sql有多个输入参数),就像苹果蓝里有很多个苹果一样,还有许多番茄蓝色的番茄。最后统一倒入仓库由于JDBC不支持数据库查询的批量处理,因此这里不再展示数据库查询的实现方法。与SimpleExecutor是一致的。我们来看看其他方法。
构造方法
public class BatchExecutor extends BaseExecutor {public static final int BATCH_UPDATE_RETURN_VALUE=Integer.MIN_VALUE + 1002;/** * 语句数组*/private final ListStatement statementsList=new ArrayList();/** * BatchResult 数组* * 每个BatchResult 元素,对应于{@link #statementList}集合中的一个Statement元素*/private final ListBatchResult batchResultList=new ArrayList();/** * 最后添加到batch中的Statement对象对应的SQL */private String currentSql;/** * 与添加到批处理中的最后一个Statement对象对应的MappedStatement对象*/private MappedStatement currentStatement; public BatchExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}}statementList 属性:维护多个Statement对象batchResultList 属性:维护多个BatchResult对象,每个对象对应上述Statement对象之一。每个BatchResult 对象都包含相同的SQL 及其每个操作的输入参数。 currentSql 属性:与最后添加到批处理的Statement 对象对应的SQLcurrentStatement 属性。批处理中添加的最后一个Statement对象对应的MappedStatement对象
BatchResult
org.apache.ibatis.executor.BatchResult:同一条SQL的聚合结果(sql和MappedStatement对象都是相等的),包括该条语句每次操作的结果同样的SQL输入参数,代码如下:
public class BatchResult {/** * MappedStatement对象*/private final MappedStatementmappedStatement;/** * SQL */private final String sql;/** * 参数对象集合* * 每个元素对应一个操作的参数*/privatefinalListObjectparameterObjects;/** * 更新数量集合* * 每个元素对应一次操作的更新数量*/private int[] updateCounts;public BatchResult(MappedStatementmappedStatement, String sql) {super();this.mappedStatement=mappedStatement ;this.sql=sql;this.parameterObjects=new ArrayList();}public BatchResult(MappedStatementmappedStatement, String sql, ObjectparameterObject) {this(mappedStatement, sql);addParameterObject(parameterObject);}public void addParameterObject(ObjectparameterObject) ) {this.parameterObjects.add(parameterObject);}}
doUpdate方法
更新数据库并添加到批处理中,需要调用doFlushStatements来执行批处理。代码如下:
@Overridepublic int doUpdate(MappedStatement ms, ObjectparameterObject) throws SQLException { 最终配置配置=ms.getConfiguration(); //1 创建StatementHandler对象final StatementHandler handler=configuration.newStatementHandler(this, ms,parameterObject, RowBounds.DEFAULT, null, null);最终BoundSql BoundSql=handler.getBoundSql();最终字符串sql=boundSql.getSql();最终声明stmt; //2 如果批量Statement对象中最后一次添加的currentSql和currentStatement一致,聚合成BatchResult if (sql.equals(currentSql) ms.equals(currentStatement)) { //2.1 获取最后添加的Statement对象到批处理int last=statementsList.size() – 1; stmt=statementsList.get(last) ; //2.2 重置事务超时applyTransactionTimeout(stmt); //2.3 在Statement中设置SQL语句上的参数,如PrepareStatement的?占位符处理程序.parameterize(stmt); //修复Issues 322 //2.4 获取最后添加的批处理语句对应的BatchResult 对象,添加本次的输入参数BatchResult batchResult=batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { //3 否则,创建Statement 和BatchResult 对象//3.1 初始化Statement 对象Connection connection=getConnection(ms.getStatementLog()); stmt=handler.prepare(连接, transaction.getTimeout()); handler.parameterize(stmt); //修复问题322 //3.2 设置currentSql 和currentStatemen currentSql=sql;当前语句=ms; //3.3 将语句添加到statementList statementsList.add(stmt); //3.4 创建BatchResult对象并将其添加到batchResultList中batchResultList.add(new BatchResult(ms, sql,parameterObject )); } //4 添加到批处理handler.batch(stmt); //5 return Integer.MIN_VALUE + 1002 return BATCH_UPDATE_RETURN_VALUE;} 创建StatementHandler对象,与SimpleExecutor一致。如果和上次添加的一样的话我们后面再说。当批量Statement对象对应的currentSql和currentStatement一致时,聚合到BatchResult中,得到批量Statement对象的最后一次添加。重置事务超时,并在Statement中设置SQL语句的参数,如?准备语句的占位符。在SimpleExecutor中已经提到要获取最后添加的批处理语句对应的BatchResult对象,并将这个输入参数添加到其中。否则,创建Statement 和BatchResult 对象来初始化Statement 对象。在SimpleExecutor中已经提到过,这里不再重复。重复设置currentSql和currentStatemen属性,将Statement添加到statementList集合中,创建BatchResult对象,并将其添加到batchResultList集合中。添加到批处理中,返回Integer.MIN_VALUE + 1002。为什么返回这个值?不清楚
doFlushStatements方法
执行批处理,即将之前添加到批处理中的数据库更新操作进行批处理。代码如下:
原创文章,作者:小su,如若转载,请注明出处:https://www.sudun.com/ask/125942.html
用户评论
嘲笑!
终于有人写了深入浅出的Executor解析!我一直很头疼Android线程这块,看这篇就明白了不少关键点,太香了!现在可以去动手实战啦~
有18位网友表示赞同!
纯真ブ已不复存在
万字讲解有点吓人,我平时只接触Java底层代码很少。不过文章结构蛮清晰的,重点突出也不突兀,还是受益匪浅!
有14位网友表示赞同!
花容月貌
讲道理,Android开发谁都逃 नहीं Executor,这篇总结得真不错。感觉作者自己都是实战经验丰富的,对源码的解读很有深度
有14位网友表示赞同!
我就是这样一个人
代码注释少了一点,感觉理解起来还是有点摸索,希望后续能完善下代码示例
有13位网友表示赞同!
一笑抵千言
我更喜欢直接看案例分析,原理讲解太抽象了,能不能以后分享一下Executor应用场景?比如图片加载框架之类的实现过程!
有6位网友表示赞同!
断桥残雪
这个标题读得我就心动,“背靠源码”, “面朝实践” 真是太对了!想当年我也是这样啃Java源码的,现在感觉很多Android开发都忽略了底层的原理啊!希望这种认真踏实的写法能被更多人看见
有15位网友表示赞同!
入骨相思
Executor这块确实很蛋疼,读代码时总是遇到各种问题。看了这篇博文后,总算是对执行器的理解有所提升了。希望你能持续更新,分享更多Android开发经验!
有20位网友表示赞同!
堕落爱人!
我最近在学习Android源码,发现Executor的处理原理的确非常重要。这篇文章讲解得很详细,特别是针对实际项目应用的解释非常有帮助。
有7位网友表示赞同!
拽年很骚
其实我对线程池的使用已经有了一定的了解,但是这篇博文还是让我发现了以前没注意的细节,例如ThreadPoolExecutor的工作机制和参数配置等等,算是提升了我的实战水平!
有15位网友表示赞同!
←极§速
写得确实不错啊,我之前一直觉得Executor很复杂,看完这篇博客感觉就简单多了。虽然文字很多,但逻辑清晰易懂,不愧是被称为“万字讲解”。
有10位网友表示赞同!
安之若素
作者分析的太深入了我有点跟不上节奏,希望能有更多简化的解释,或者直接提供一些常见案例分析
有16位网友表示赞同!
三年约
这种理论讲解真的让我乏味,我更喜欢看实际应用案例。希望文章能结合实战场景,说明Executor如何应用于实际项目中解决问题!
有7位网友表示赞同!
尘埃落定
看了标题以为是深度解析 Executor 各个类型的用法,但好像更多偏重理论基础,不够具体的操作示例,有点失望!
有13位网友表示赞同!
娇眉恨
我个人觉得这篇文章写的太枯燥了,如果能加入一些图片和动画来辅助理解,效果肯定更好 。
有8位网友表示赞同!
赋流云
代码部分我觉得可以更简明扼要些,毕竟万字篇文章已经足够长了,把重点放在实践经验分享上,也许更有吸引力!
有17位网友表示赞同!
落花忆梦
我承认文章水平确实很高,但对于初学者来说,一些概念性的解释还是比较难理解。希望作者能对一些关键知识点再进行详细的讲解。
有6位网友表示赞同!
身影
感觉作者很注重理论深度,但缺乏实战经验分享,对于想快速掌握Executor同学来说可能不太合适!
有17位网友表示赞同!
短发
文章写得不错,但是我觉得对于 Android 开发者来说,更重要的是了解 Executor 如何应用于实际项目中。希望可以多提供一些案例分析和代码示例!
有15位网友表示赞同!
﹏櫻之舞﹏
万字讲解有点长了,我希望能直接看到重点内容,比如Executor的常见使用场景和优缺点总结。
有9位网友表示赞同!