zoukankan      html  css  js  c++  java
  • Mybatis 源码(二)-三种执行器Executor 的区别

      mybatis有三种执行器。下面研究其主要区别。

    其类图如下:

    1. Executor 源码

    org.apache.ibatis.executor.Executor:

    package org.apache.ibatis.executor;
    
    import java.sql.SQLException;
    import java.util.List;
    
    import org.apache.ibatis.cache.CacheKey;
    import org.apache.ibatis.cursor.Cursor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.transaction.Transaction;
    
    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);
    
    }

    2. BaseExecutor 抽象类

    public abstract class BaseExecutor implements Executor {
    
      private static final Log log = LogFactory.getLog(BaseExecutor.class);
    
      protected Transaction transaction;
      protected Executor wrapper;
    
      protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
      protected PerpetualCache localCache;
      protected PerpetualCache localOutputParameterCache;
      protected Configuration configuration;
    
      protected int queryStack;
      private boolean closed;
    
      protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<>();
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
      }
    
      @Override
      public Transaction getTransaction() {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        return transaction;
      }
    
      @Override
      public void close(boolean forceRollback) {
        try {
          try {
            rollback(forceRollback);
          } finally {
            if (transaction != null) {
              transaction.close();
            }
          }
        } catch (SQLException e) {
          // Ignore. There's nothing that can be done at this point.
          log.warn("Unexpected exception on closing transaction.  Cause: " + e);
        } finally {
          transaction = null;
          deferredLoads = null;
          localCache = null;
          localOutputParameterCache = null;
          closed = true;
        }
      }
    
      @Override
      public boolean isClosed() {
        return closed;
      }
    
      @Override
      public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
      }
    
      @Override
      public List<BatchResult> flushStatements() throws SQLException {
        return flushStatements(false);
      }
    
      public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        return doFlushStatements(isRollBack);
      }
    
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    
      @SuppressWarnings("unchecked")
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    
      @Override
      public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return doQueryCursor(ms, parameter, rowBounds, boundSql);
      }
    
      @Override
      public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
        if (deferredLoad.canLoad()) {
          deferredLoad.load();
        } else {
          deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
        }
      }
    
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
          }
        }
        if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }
    
      @Override
      public boolean isCached(MappedStatement ms, CacheKey key) {
        return localCache.getObject(key) != null;
      }
    
      @Override
      public void commit(boolean required) throws SQLException {
        if (closed) {
          throw new ExecutorException("Cannot commit, transaction is already closed");
        }
        clearLocalCache();
        flushStatements();
        if (required) {
          transaction.commit();
        }
      }
    
      @Override
      public void rollback(boolean required) throws SQLException {
        if (!closed) {
          try {
            clearLocalCache();
            flushStatements(true);
          } finally {
            if (required) {
              transaction.rollback();
            }
          }
        }
      }
    
      @Override
      public void clearLocalCache() {
        if (!closed) {
          localCache.clear();
          localOutputParameterCache.clear();
        }
      }
    
      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;
    
      protected void closeStatement(Statement statement) {
        if (statement != null) {
          try {
            statement.close();
          } catch (SQLException e) {
            // ignore
          }
        }
      }
    
      /**
       * Apply a transaction timeout.
       *
       * @param statement
       *          a current statement
       * @throws SQLException
       *           if a database access error occurs, this method is called on a closed <code>Statement</code>
       * @since 3.4.0
       * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
       */
      protected void applyTransactionTimeout(Statement statement) throws SQLException {
        StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
      }
    
      private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
        if (ms.getStatementType() == StatementType.CALLABLE) {
          final Object cachedParameter = localOutputParameterCache.getObject(key);
          if (cachedParameter != null && parameter != null) {
            final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
            final MetaObject metaParameter = configuration.newMetaObject(parameter);
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
              if (parameterMapping.getMode() != ParameterMode.IN) {
                final String parameterName = parameterMapping.getProperty();
                final Object cachedValue = metaCachedParameter.getValue(parameterName);
                metaParameter.setValue(parameterName, cachedValue);
              }
            }
          }
        }
      }
    
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    
      protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
          return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
          return connection;
        }
      }
    
      @Override
      public void setExecutorWrapper(Executor wrapper) {
        this.wrapper = wrapper;
      }
    
      private static class DeferredLoad {
    
        private final MetaObject resultObject;
        private final String property;
        private final Class<?> targetType;
        private final CacheKey key;
        private final PerpetualCache localCache;
        private final ObjectFactory objectFactory;
        private final ResultExtractor resultExtractor;
    
        // issue #781
        public DeferredLoad(MetaObject resultObject,
                            String property,
                            CacheKey key,
                            PerpetualCache localCache,
                            Configuration configuration,
                            Class<?> targetType) {
          this.resultObject = resultObject;
          this.property = property;
          this.key = key;
          this.localCache = localCache;
          this.objectFactory = configuration.getObjectFactory();
          this.resultExtractor = new ResultExtractor(configuration, objectFactory);
          this.targetType = targetType;
        }
    
        public boolean canLoad() {
          return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
        }
    
        public void load() {
          @SuppressWarnings("unchecked")
          // we suppose we get back a List
          List<Object> list = (List<Object>) localCache.getObject(key);
          Object value = resultExtractor.extractObjectFromList(list, targetType);
          resultObject.setValue(property, value);
        }
    
      }
    
    }
    View Code

      可以看到是模板模式的一种设计。其暴露了一些doXXX 方法供下面的具体的Executor 类实现。

    3. 具体的Executor

    1. org.apache.ibatis.executor.SimpleExecutor 具体的Executor, 每次doQuery、doUpdate 都会创建Statement 对象。默认也是这个Executor

    package org.apache.ibatis.executor;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Collections;
    import java.util.List;
    
    import org.apache.ibatis.cursor.Cursor;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.session.Configuration;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.transaction.Transaction;
    
    public class SimpleExecutor extends BaseExecutor {
    
      public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
      }
    
      @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();
      }
    
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
      }
    
    }

    2. org.apache.ibatis.executor.ReuseExecutor 可重用的Executor, 也就是会缓存Statement 对象,重复使用statement。

    package org.apache.ibatis.executor;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.ibatis.cursor.Cursor;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.session.Configuration;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.transaction.Transaction;
    
    public class ReuseExecutor extends BaseExecutor {
    
      private final Map<String, Statement> statementMap = new HashMap<>();
    
      public ReuseExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
      }
    
      @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);
      }
    
      @Override
      public List<BatchResult> doFlushStatements(boolean isRollback) {
        for (Statement stmt : statementMap.values()) {
          closeStatement(stmt);
        }
        statementMap.clear();
        return Collections.emptyList();
      }
    
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        if (hasStatementFor(sql)) {
          stmt = getStatement(sql);
          applyTransactionTimeout(stmt);
        } else {
          Connection connection = getConnection(statementLog);
          stmt = handler.prepare(connection, transaction.getTimeout());
          putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
      }
    
      private boolean hasStatementFor(String sql) {
        try {
          Statement statement = statementMap.get(sql);
          return statement != null && !statement.getConnection().isClosed();
        } catch (SQLException e) {
          return false;
        }
      }
    
      private Statement getStatement(String s) {
        return statementMap.get(s);
      }
    
      private void putStatement(String sql, Statement stmt) {
        statementMap.put(sql, stmt);
      }
    
    }

      可以看到getStatement 方法会以sql为key,去缓存先拿statement 对象。

    3. org.apache.ibatis.executor.BatchExecutor

      可以执行批量update,也就是可以执行多条语句。可以批量修改、插入,也就是使用preparedStatement 对象,后面传入多个参数。

    package org.apache.ibatis.executor;
    
    import java.sql.BatchUpdateException;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    import org.apache.ibatis.cursor.Cursor;
    import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
    import org.apache.ibatis.executor.keygen.KeyGenerator;
    import org.apache.ibatis.executor.keygen.NoKeyGenerator;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.session.Configuration;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.transaction.Transaction;
    
    public class BatchExecutor extends BaseExecutor {
    
      public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
    
      private final List<Statement> statementList = new ArrayList<>();
      private final List<BatchResult> batchResultList = new ArrayList<>();
      private String currentSql;
      private MappedStatement currentStatement;
    
      public BatchExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
      }
    
      @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;
      }
    
      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException {
        Statement stmt = null;
        try {
          flushStatements();
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
          Connection connection = getConnection(ms.getStatementLog());
          stmt = handler.prepare(connection, transaction.getTimeout());
          handler.parameterize(stmt);
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
      @Override
      protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
        flushStatements();
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
        Connection connection = getConnection(ms.getStatementLog());
        Statement stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        Cursor<E> cursor = handler.queryCursor(stmt);
        stmt.closeOnCompletion();
        return cursor;
      }
    
      @Override
      public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
        try {
          List<BatchResult> results = new ArrayList<>();
          if (isRollback) {
            return Collections.emptyList();
          }
          for (int i = 0, n = statementList.size(); i < n; i++) {
            Statement stmt = statementList.get(i);
            applyTransactionTimeout(stmt);
            BatchResult batchResult = batchResultList.get(i);
            try {
              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())) { //issue #141
                for (Object parameter : parameterObjects) {
                  keyGenerator.processAfter(this, ms, stmt, parameter);
                }
              }
              // Close statement to close cursor #1109
              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);
            }
            results.add(batchResult);
          }
          return results;
        } finally {
          for (Statement stmt : statementList) {
            closeStatement(stmt);
          }
          currentSql = null;
          statementList.clear();
          batchResultList.clear();
        }
      }
    
    }

    类似于JDBC提供的批处理,其优缺点如下:

    优点:发送的是预编译后的SQL语句,执行效率高。

    缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。

    4. 测试

    1. mybatis-config.xml 修改ExecutorType类型为BATCH

      <settings>
        <setting name="defaultExecutorType" value="BATCH"/>
      </settings>

    2. 解析原理:

    在构造SqlSessionFactory 过程中会调用 org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration:

      private void parseConfiguration(XNode root) {
        try {
          // issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

    1》 settingsAsProperties 解析settings为properties 属性

    2》org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement 解析 props 属性, 可以看到有一些全局的配置信息,后面会根据这些信息进行初始化

      private void settingsElement(Properties props) {
        configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
        configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
        configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
        configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
        configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
        configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
        configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
        configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
        configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        configuration.setLogPrefix(props.getProperty("logPrefix"));
        configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
        configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
        configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
      }

    3. 测试修改:

      @Test
      void updateProjectTest() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
          final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
          mapper.updateSubject(1, "1");
          System.out.println("111222");
          mapper.updateSubject(1, "2");
        }
      }

    查看日志如下:

    DEBUG [main] - ==>  Preparing: update subject set name = ? WHERE id = ?
    DEBUG [main] - ==> Parameters: 1(Integer), 1(String)
    111222
    DEBUG [main] - ==> Parameters: 1(Integer), 2(String)
    DEBUG [main] - Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
    DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
    DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]

      可以看到发了一次SQL

    4. 使用默认的 SimpleExecutor 查看默认的SQL如下:

    DEBUG [main] - ==>  Preparing: update subject set name = ? WHERE id = ?
    DEBUG [main] - ==> Parameters: 1(Integer), 1(String)
    DEBUG [main] - <==    Updates: 1
    111222
    DEBUG [main] - ==>  Preparing: update subject set name = ? WHERE id = ?
    DEBUG [main] - ==> Parameters: 1(Integer), 2(String)
    DEBUG [main] - <==    Updates: 2

    5. CachingExecutor

      缓存执行器。是一个Executor的装饰器。它为Executor对象增加了二级缓存的相关功能,委托的执行器对象可以是SimpleExecutor、ReuseExecutor、BatchExecutor中任一一个。

      执行 update 方法前判断是否清空二级缓存;执行 query 方法前先在二级缓存中查询,命中失败在通过被代理类查询。

    package org.apache.ibatis.executor;
    
    import java.sql.SQLException;
    import java.util.List;
    
    import org.apache.ibatis.cache.Cache;
    import org.apache.ibatis.cache.CacheKey;
    import org.apache.ibatis.cache.TransactionalCacheManager;
    import org.apache.ibatis.cursor.Cursor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.ParameterMapping;
    import org.apache.ibatis.mapping.ParameterMode;
    import org.apache.ibatis.mapping.StatementType;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.transaction.Transaction;
    
    public class CachingExecutor implements Executor {
    
      private final Executor delegate;
      private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
      public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
      }
    
      @Override
      public Transaction getTransaction() {
        return delegate.getTransaction();
      }
    
      @Override
      public void close(boolean forceRollback) {
        try {
          // issues #499, #524 and #573
          if (forceRollback) {
            tcm.rollback();
          } else {
            tcm.commit();
          }
        } finally {
          delegate.close(forceRollback);
        }
      }
    
      @Override
      public boolean isClosed() {
        return delegate.isClosed();
      }
    
      @Override
      public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
      }
    
      @Override
      public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.queryCursor(ms, parameter, rowBounds);
      }
    
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    
      @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); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    
      @Override
      public List<BatchResult> flushStatements() throws SQLException {
        return delegate.flushStatements();
      }
    
      @Override
      public void commit(boolean required) throws SQLException {
        delegate.commit(required);
        tcm.commit();
      }
    
      @Override
      public void rollback(boolean required) throws SQLException {
        try {
          delegate.rollback(required);
        } finally {
          if (required) {
            tcm.rollback();
          }
        }
      }
    
      private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
        if (ms.getStatementType() == StatementType.CALLABLE) {
          for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
            if (parameterMapping.getMode() != ParameterMode.IN) {
              throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
            }
          }
        }
      }
    
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
      }
    
      @Override
      public boolean isCached(MappedStatement ms, CacheKey key) {
        return delegate.isCached(ms, key);
      }
    
      @Override
      public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        delegate.deferLoad(ms, resultObject, property, key, targetType);
      }
    
      @Override
      public void clearLocalCache() {
        delegate.clearLocalCache();
      }
    
      private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
          tcm.clear(cache);
        }
      }
    
      @Override
      public void setExecutorWrapper(Executor executor) {
        throw new UnsupportedOperationException("This method should not be called");
      }
    
    }

       而且默认SqlSession 中的Executor 是CachingExecutor, CachingExecutor 内部维护的代理delegate是 SimpleExecutor。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    iframe显示高度自适应 兼容多浏览器
    Asp.Net在Global.asax中实现URL 的重写
    文件流下载 ASP.NET
    Asp.Net实现全局定时器功能
    C#中获取本机IP地址,子网掩码,网关地址
    Asp.Net缓存实例
    Google API 天气数据缓存到一个XML中
    获取服务器信息
    C# NET 中英混合字符串截断实例
    Asp.Net实现长文章分页显示功能
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15230019.html
Copyright © 2011-2022 走看看