您现在的位置是:首页 > 文章详情

Mybatis 背后的强者Executor

日期:2020-01-05点击:438

Mybatis 背后的强者Executor

当我们读取完配置文件,将我们的Mybatis配置成我们想要的要的样子之后,我们就要使用他对数据库进行一系列操作(增删改查)。而SqlSession这个看似无所不能的操作达人,其实是找了代练的。SqlSession将一切数据库具体操作委托给背后的强者,今天要就让我们揭开Executor这个强者的面纱。

Executor 类结构图

可以看出强者的家族都是一脉相承的,让我们逐一认识一下这一家人。

public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement ms, Object parameter) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; List<BatchResult> flushStatements() throws SQLException; void commit(boolean required) throws SQLException; void rollback(boolean required) throws SQLException; CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); boolean isCached(MappedStatement ms, CacheKey key); void clearLocalCache(); void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); Transaction getTransaction(); void close(boolean forceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor); } 

Executor接口就好似一个武林秘籍,涵盖了包括查询,更新,事务操作,缓存构建的一系列描述,根据方法名我们可略知一二。得此秘籍者两人却分别走了两条不同的修行之路。

桃李天下的 BaseExecutor

BaseExecutor 作为一个合格的老师,为学生铺好了练习的场地,处理了诸如数据库连接,数据库关闭,事务回滚,事务提交等一系列繁杂的事情。学生只需专心于以下4技能的修炼即可.关键 时刻,学生出拳就能了事。

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException; 

接下来让我们分别看看,他的四大弟子都有什么本事吧。

SimpleExecutor : 应对简单处理,每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象),俗称打完就跑,有始有终。

BatchExecutor:善于批量处理执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;

ReuseExecutor :一旦出拳,不叫停不会停。执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。

ClosedExecutor :最不学无术的一个 ,是ResultLoaderMap的一个内部类,只返回一些标志位。

听完他们的本事,是不是很想知道他们具体怎么成为高徒的呢,秀肌肉到的是时候到了。

1、SimpleExecutor

@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } @Override protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); Cursor<E> cursor = handler.queryCursor(stmt); stmt.closeOnCompletion(); return cursor; } @Override public List<BatchResult> doFlushStatements(boolean isRollback) { return Collections.emptyList(); } 

SimpleExecutor 较为简单,无论是query 还是 update statement随建随关。基本步骤就是获取配置,创建相应的StatementHandler,实例化Statement ,使用StatementHandler执行数据库操作。

2、ReuseExecutor

private final Map<String, Statement> statementMap = new HashMap<>(); @Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } @Override protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.queryCursor(stmt); } 

ReuseExecutor 和 SimpleExecutor 的操作步骤基本相似,最大的区别在于前者维护者一个statementMap 用于记录并没有销毁的statement,当相同的sql语句被执行时,会使用同一个已经创建好的statement,如果sql第一次执行,那么就创建一个新的statement,并放入statementMap中。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); //有则直接使用 if (hasStatementFor(sql)) { //sql 作为 key,statement 作为value stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { // 没有则创建新的statement Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } 

当然ReuseExecutor 中的statement 不可能一直不关闭,这就需要我们在恰当的时候,通过调用doFlushStatements方法手动对其进行关闭。

@Override public List<BatchResult> doFlushStatements(boolean isRollback) { for (Statement stmt : statementMap.values()) { closeStatement(stmt); } statementMap.clear(); return Collections.emptyList(); } 

3、BatchExecutor

BatchExecutor 是几个徒弟之中拳法最为厉害的,他可以进行批量操作,让我们看看他是如何以一敌百的。

让我们先来看看BatchExecutor 是处理每一条sql的。

(引用自 网络资源 “Mybatis3.3.x技术内幕(四):五鼠闹东京之执行器Executor设计原本”)

BatchExecutor 在执行批量更新时,根据sql语句创建Statement桶,相同的sql使用相同statement,并将所有的statement添加到statementList中。

在执行批量更新的过程中,并不执行sql,只是拼接不同的statement。直至commit时,拿到statementList逐一进行sql执行。

//维护一个Statement列表 用于缓存拼接好的statement private final List<Statement> statementList = new ArrayList<>(); //维护一个Result 列表,用于记录处理结果 private final List<BatchResult> batchResultList = new ArrayList<>(); //当前保存的sql,也就是上一次执行的sql private String currentSql; ////当前保存的MappedStatement,也就是上一次执行的MappedStatement private MappedStatement currentStatement; @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(); //要执行的sql final String sql = boundSql.getSql(); final Statement stmt; //如果和上次执行sql相同,同时MappedStatement也必须相同,取上次执行statement,并传入此次参数 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 { //如果和上次执行sql不同,创建新的statement 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内部执行addBatch()方法 handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } 

需要注意:

sql.equals(currentSql)和statementList.get(last),充分说明了其有序逻辑:AABB,将生成2个Statement对象;AABBAA,将生成3个Statement对象,而不是2个。因为,只要sql有变化,将导致生成新的Statement对象。也就是说,现在执行的sql,只和上一次执行的sql进行对比,是否相同。如果相同使用上一个statement,如果不同再次创建新的statement。

那么什么时候执行sql 呢?

答案是:在执行commit的时候。在执行commit、rollback等动作前,都将会执行flushStatements()方法,将Statement对象逐一关闭。

看看BatchExecutor .doFlushStatements方法。

@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<>(); if (isRollback) { return Collections.emptyList(); } //遍历statementList,执行sql for (int i = 0, n = statementList.size(); i < n; i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { // 执行sql batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { for (Object parameter : parameterObjects) { keyGenerator.processAfter(this, ms, stmt, parameter); } } // 关闭statement closeStatement(stmt); } catch (BatchUpdateException e) { StringBuilder message = new StringBuilder(); message.append(batchResult.getMappedStatement().getId()) .append(" (batch index #") .append(i + 1) .append(")") .append(" failed."); if (i > 0) { message.append(" ") .append(i) .append(" prior sub executor(s) completed successfully, but will be rolled back."); } throw new BatchExecutorException(message.toString(), e, results, batchResult); } //将每次结果添加到resultList中。 results.add(batchResult); } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null; statementList.clear(); batchResultList.clear(); } } 

偷奸耍滑的师叔CachingExecutor

第一次听到CachingExecutor 的名字的时候,以为它是所有Executor 中最厉害的一个。看名字就知道CachingExecutor在完成sql执行的任务之外,还做了缓存工作。作为一个同样得到秘籍(直接实现Executor接口)的师叔,其实他就是一个偷奸耍滑的老头子。

 private final Executor delegate; public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } 

通过他的构造函数我们可以清楚的看出,CachingExecutor 其实还是将所有的sql执行任务交给了不同Executor(SimpleExecutor、ReuseExecutor、BatchExecutor),而自己主要进行缓存处理。

关于mybatis的缓存机制,会有单独的专题进行分析总结

@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } @Override public List<BatchResult> flushStatements() throws SQLException { return delegate.flushStatements(); } 
原文链接:https://my.oschina.net/u/4435073/blog/3153673
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章