mybatis升级导致的一次异常

这个故事是这样开始的,某部门的小伙伴,突然有一天找我,火急火燎的说线上业务出问题了。之前线上运行的代码没有任何问题,突然某功能的一条 SQL 执行异常。之前是好好的啊,怎么突然就不行了。查询了好多资料,也检查了代码,也不知道错在了哪里。
通过分析,发现了一个很奇怪的现象,SQL 语句写的是没有任何问题的,通过开启 SQL 语句 Debug 打印,其实也显示查询到了记录,但是返回给业务层的时候,始终没有数据,返回 java.lang.NullPointerException 异常。
根据经验判断,应该是 mybatis 代码的问题,经过询问得知,小伙伴使用了一个开源框架,他升级了这个框架,附带地 mybatis 的版本也跟着升级了。
有了这个答案,似乎问题就简单很多了,我们只要找到问题是如何引起的就解决了。我们首先想到应该是mybatis在做 resultMap 映射的时候出了异常。我们进入 ResultSetHandler 的实现类 DefaultResultSetHandler 中寻找答案。

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // 处理单行RowValue
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }


  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

// 处理自动映射
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }


// 创建自动映射,主要是在这个方法中
    private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    // 解析出未映射的列,返回的结果
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<>();
      // 从resultSet中获取未映射的列名
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      // 循环分析处理
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
          // When columnPrefix is specified,
          // ignore columns without the prefix.
          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            propertyName = columnName.substring(columnPrefix.length());
          } else {
            continue;
          }
        }
        // 看下列在最终的对象中是否存在
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
        // 如果存在,就进行自动映射处理
        if (property != null && metaObject.hasSetter(property)) {
            /* 如果resultMap中包含这个列,退出本轮循环。
            问题就出在这里,如果查询返回的列在resultMap中有显示定义,那么就会结束本次处理;
            这时候存在这样一个问题,如果一张表中有5个字段,resultMap中有三个字段,
            且对应于表中三个字段,那么mybatis会认为这三个字段已经映射了,
            导致autoMapping为空,这时候就会出现业务返回空指针,这个应该是一个bug
            */

          if (resultMap.getMappedProperties().contains(property)) {
            continue;
          }
          final Class<?> propertyType = metaObject.getSetterType(property);
          if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
            autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
          } else {
            configuration.getAutoMappingUnknownColumnBehavior()
                .doAction(mappedStatement, columnName, property, propertyType);
          }
        } else {
          configuration.getAutoMappingUnknownColumnBehavior()
              .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
        }
      }
      autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
  }

通过上面的代码分析,我们找到了问题的根源,mybatis 会把表中返回的字段能够对应 resultMap 属性字段过滤掉。这就导致当返回的字段和 resultMap 全部对应时,返回的 autoMapping 映射 List 为空,自然导致映射失败。
既然我们已经知道了问题存在这一行代码,那怎么修改呢?

     if (resultMap.getMappedProperties().contains(property)) {
            continue;
          }

这里有几种方法:一、是使用 resultType,因为我们问题是单表的查询直接映射,理论上讲应该使用 resultType,这时候不去解析 resultMap 映射,这样就避免了过滤,但是线上有可能还有其他功能存在这样的使用情况,逐一去修改是不可能的。
二、回退框架 ,回退到设计之前的版本,但是小伙伴反馈,以基于新版本开发,线上已经有新版本特性的代码,回退是不可能回退的,这辈子都不可能回退的,其他的也干不了,只能依靠第三种方法。
三、比对了升级的代码,发现 mybatis 从 3.4.3 引入了这行代码,日志显示是为了解决复杂 map 的嵌套 bug,避免无意义的解析。在我们的实际使用场景中,没有使用这样的情况,于是我们决定走第三种方案,修改源码。
但是问题来了,修改了源码将来再次升级 mybatis 怎么办,会不会忘记这个 bugfix?最终我们决定这样做,我们将这个文件单独修改,同时生成一个 bugfix 的 jar 包,基于 JVM 的 classLoader 双亲委托特性,想办法让 JVM 优先加载我们 jar 包里的这个类,这样既避免了修改 mybatis 源码,也可以不用修改线上业务代码,还能解决我们的问题,将来也可以平滑升,一举多得。最终经过验证,成功解决。

总结

-   对于不熟悉的框架,升级需谨慎;
-   开源的框架问题,不要害怕,勇敢的看他的源码;
-   遇到解决不了的问题,不妨换个思路,从其他方向寻找突破。这才是这次问题最宝贵的经验,问题本身不重要,重要的是解决的思路。

爬虫Scrapy笔记(五)、middlewares

实战中我们的爬虫很容易被对方ban,如何反ban是一门很深的功课。我们来学学最简单的。
通常情况下,最容易想到的有两个,一个是用户代理(user-agent),还有一个就是ip代理(proxy)。下面我们就要学一个新的组件middlewares。
middlewares组件是一组中间件,主要是处理爬虫系统的输入输出数据,包括request和response数据。
首先需要自定义一个middleware组件,随便起个名字SampleUserAgentMiddleware,用户代理的middleware继承scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware,然后实现他的process_request方法。具体实现是这样的,我们定义一个用户代理的列表,也可以动态生成,简单起见,我们写个固定的列表,然后随机选取一个,之后把它塞到request请求的User-Agent参数中即可。

class SampleUserAgentMiddleware(useragent.UserAgentMiddleware):
    #代理
    agents = [
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
        "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; \.NET CLR 3.0.04506)",
        "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
        "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
        "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
        "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
        "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
        "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
        "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
        "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
        ]

    def __init__(self, user_agent=''):
        self.user_agent = user_agent

    def process_request(self, request, spider):
        #随机选择一个user-agent
        agent = random.choice(self.agents)
        request.headers.setdefault('User-Agent', agent)

接下来我们再来实现一个ip代理,也是很简单的,我们定义一个middleware组件,然后实现process_request方法就可以了,这里我们演示采用fiddler代理。

class ProxyMiddleware(object):
    def process_request(self, request, spider):
        proxy = 'http://localhost:8888'
        request.meta['proxy'] = proxy

要想middleware工作,我们还需要在setting中开启配置,

DOWNLOADER_MIDDLEWARES = {
#    'sample.middlewares.MyCustomDownloaderMiddleware': 543,2019-09-04 13:57:43 星期三
    'sample.middlewares.SampleUserAgentMiddleware': 100,
    'sample.middlewares.ProxyMiddleware': 200,
}

我们来验证下

我们可以看到代理成功获取。

sample示例源码下载

总结

总的来说,通过scrapy来实现爬虫还是很简单的。因其高度封装特性,以及模块化的设计,极大地便利了开发者。但是想要用做好爬虫还有很远的路要走,比如反爬,分布式爬虫的设计等等。

Mybatis源码笔记(六)、ResultHandler

ResultHandler 接口比较简单,主要用于处理 ResultContext 对象

public interface ResultHandler<T> {

  void handleResult(ResultContext<? extends T> resultContext);

}

ResultHandler 主要有两个实现,分别是 DefaultResultHandler 和 DefaultMapResultHandler

  @SuppressWarnings("unchecked")
  public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    this.mappedResults = objectFactory.create(Map.class);
    this.mapKey = mapKey;
  }

  @Override
  public void handleResult(ResultContext<? extends V> context) {
    final V value = context.getResultObject();
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    // TODO is that assignment always true?
    final K key = (K) mo.getValue(mapKey);
    mappedResults.put(key, value);
  }

  public Map<K, V> getMappedResults() {
    return mappedResults;
  }
}

public class DefaultResultHandler implements ResultHandler<Object> {

  private final List<Object> list;

  public DefaultResultHandler() {
    list = new ArrayList<>();
  }

  @SuppressWarnings("unchecked")
  public DefaultResultHandler(ObjectFactory objectFactory) {
    list = objectFactory.create(List.class);
  }

  @Override
  public void handleResult(ResultContext<?> context) {
    list.add(context.getResultObject());
  }

  public List<Object> getResultList() {
    return list;
  }

}

区别的化一目了然,DefaultMapResultHandler 将处理结果存储在一个 Map 对象中,DefaultResultHandler 将结果存储在一个 List 对象中。

总结:

至此,我们的主线流程大致的也就有了一些了解,对应于传统的 JDBC 方式,我们一步一步地分析了源码,他们是如何运作的,基本上已经明了了。实际上mybatis的框架还是很复杂的,其中一些很复杂的逻辑我们并没有实际深入,比如boundSql的解析,自动填充对象的映射,等等。想要有更深的了解还需进一步精度这些代码。

Mybatis源码笔记(五)、ResultSetHandler

StatementHandler 返回的结果主要是通过 ResultSetHandler 接口来处理,BaseStatementHandler 的构造方法中有如下代码初始化 StatementHandler,返回的是 StatementHandler 的实现类

 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);```
 通过configration的newResultSetHandler返回ResultSetHandler对象。

   public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
        // 这里返回的是DefaultResultSetHandler对象
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }
   // DefaultResultSetHandler实现了ResultSetHandler
  public class DefaultResultSetHandler implements ResultSetHandler {
    ......
  }

ResultSetHandler 接口的方法如下

public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

我们来开下 DefaultResultSetHandler 的具体实现

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 获取第一个结果集处理
    ResultSetWrapper rsw = getFirstResultSet(stmt);

// 从mappedStatement获取解析出的ResultMaps
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 判断是否设置了resultMap或resultSet
    validateResultMapsCount(rsw, resultMapCount);
    // 处理返回结果resultMap
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 通过mappedStatement获取多结果集数组对象
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      // 循环处理结果
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }


    //处理结果集
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        // 如果resultHandler为空,构建一个对象去处理
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            // 处理结果集行数据
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

  // 通过ResultHandler处理result
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        // 处理简单映射
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }


    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 获取行记录
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        //通过resultHandler操作存储结果
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }
// 行记录的处理
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //  创建返回的result映射对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
        // 是否需要自动映射
      if (shouldApplyAutomaticMappings(resultMap, false)) {
          // 该方法完成自动映射,逻辑很是很复杂的,具体的不深究
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
      linkToParents(rs, parentMapping, rowValue);
    } else {
      callResultHandler(resultHandler, resultContext, rowValue);
    }
  }
// resultHandler处理结果
  @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  }

至此,我们可以清晰地看到 ResultSetHandler 根据 mappedStatement 中设置的 ResultMap来处理 statement 执行返回的结果,处理映射的逻辑写的还是很精彩的,考虑的比较全面。

下一节,我们将看下 ResultHandler 是如何处理存储返回的 rowValue 的。

Mybatis源码笔记(四)、StatementHandler

在上一节中我们看到,Statement 处理主要由 StatementHandler 接口实现,StatementHandler 由 configuration.newStatementHandler 创建

 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);


public StatementHandler newStatementHandler(Executor executor,
MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

//   根据 不同的类型创建不同的StatementHandler
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

我们看一下 StatementHandler 接口的方法

public interface StatementHandler {

Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;

void parameterize(Statement statement)
throws SQLException;

void batch(Statement statement)
throws SQLException;

int update(Statement statement)
throws SQLException;

<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;

<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;

BoundSql getBoundSql();

ParameterHandler getParameterHandler();

}

我们选取其中的 SimpleStatementHandler 来具体分析下,SimpleStatementHandler 继承了 BaseStatementHandler,BaseStatementHandler 实现了 StatementHandler 接口


public abstract class BaseStatementHandler implements StatementHandler { // 配置 protected final Configuration configuration; protected final ObjectFactory objectFactory; protected final TypeHandlerRegistry typeHandlerRegistry; //结果处理 protected final ResultSetHandler resultSetHandler; //参数处理 protected final ParameterHandler parameterHandler; // 具体的执行器 protected final Executor executor; // mappedStatement protected final MappedStatement mappedStatement; ...... } // 继承实现类SimpleStatementHandler public class SimpleStatementHandler extends BaseStatementHandler { ...... @Override public int update(Statement statement) throws SQLException { // 执行SQL String sql = boundSql.getSql(); // SQL param Object parameterObject = boundSql.getParameterObject(); // 主键生成方式 KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); int rows; // 根据不同的keyGenerator,传入 不同的参数执行,最后返回结果 if (keyGenerator instanceof Jdbc3KeyGenerator) { // 很熟悉的味道 statement.execute(sql, Statement.RETURN_GENERATED_KEYS); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else if (keyGenerator instanceof SelectKeyGenerator) { statement.execute(sql); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else { statement.execute(sql); rows = statement.getUpdateCount(); } // 返回结果 return rows; } ...... }

上面的 update 方法我们终于找到了熟悉的东西,JDBC 的 statement 执行,至此,一切都清楚了。其实万变不离其宗,只是框架让我们可以脱离一些低级、重复劳动,优化性能,比我们自己徒手实现的要好。但是归根结底还是基础,所以基础还是要打牢的。

Mybatis源码笔记(三)、Executor

闲言少叙,书接上文,上一节我们读到 session 的执行,这一节我们来看下 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);
}

在前面章节的 openSession 中我们介绍了 Executor 的创建,主要包含如下三种 Executor,分别是批量 BatchExecutor、复用 ReuseExecutor、以及简单 SimpleExecutor,默认是 SimpleExecutor。

// 获取Executor
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 是否启用缓存,采用装饰器模式
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

我们可以发现,这三种 Executor 都继承了抽象类 BaseExecutor

public class BatchExecutor extends BaseExecutor {
 ......
}
public class ReuseExecutor extends BaseExecutor {
 ......
}
public class SimpleExecutor extends BaseExecutor {
 ......
}

我们继续追踪到 Executor,我们看看上一节的 update 实现

public abstract class BaseExecutor implements Executor {
......
// Executor的wrapper对象,如果开启缓存,那么会创建Executor的代理CachingExecutor,
// 通过delegate.setExecutorWrapper(this)将CachingExecutor设置进来,后期的缓存操作就委托给了子类,
// 由于mybatis本身的实现问题,在分布式环境下有可能会存在脏数据问题,所以开启缓存是要注意
  protected Executor wrapper;
 ......


  @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();
    // 最终进入到doUpdate方法
    return doUpdate(ms, parameter);
  }

// 抽象方法,具体的继承类实现
    protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

}

上述代码可以看到,具体的操作其实是委托给子类进行处理的。看一下具体的实现

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();
    //   获取handler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
    //   交由handler执行
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
  ......
}

总结:

这一节我们主要看了 Executor 极其实现,鉴于篇幅,我们选取了其中一个方法来说明,其实还是很简单的。下一节我们将看下 Statement 是如何执行的。

Mybatis源码笔记(二)、SqlSession

书接前文,我们这一节来看看 SqlSession。


// SqlSession 是 mybatis 主要的接口之一,通过这个接口, // 我们可以实现执行命令,获取 mappers,以及事务的管理 public interface SqlSession extends Closeable { ...... }

该接口的方法大致如下,有了这些方法,我们就可以进行各种操作了
sqlSession
上一节中,我们通过 SqlSessionFactoryBean 的 buildSqlSessionFactory 方法获得到了 DefaultSqlSessionFactory。下面我们来看下是如何通过 SqlSessionFactory 获得 SqlSession 的,我们取其中的一个例子。


@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取Environment,Environment对象中有TransactionFactory和DataSource对象, // 使用经典的建造者模式,通过链式调用返回创建。 final Environment environment = configuration.getEnvironment(); // 通过environment 获取transactionFactory,来创建transaction, // 这里的autoCommit为false,即默认不自动提交。 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 根据不同的执行类型获取Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 这一步,创建了SqlSession的一个实现类DefaultSqlSession, // 这样我们就获取到了SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { // 异常了关闭Transaction closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } // 这个关闭很简单,就是异常了如果Transaction对象不为空,关闭,做垃圾清理 private void closeTransaction(Transaction tx) { if (tx != null) { try { tx.close(); } catch (SQLException ignore) { // Intentionally ignore. Prefer previous error. } } }

好了,我么来看下 SqlSession 的实现

public class DefaultSqlSession implements SqlSession {
// 获取配置
  private final Configuration configuration;
  // 这个是核心,后面的执行都是他来做的。
  private final Executor executor;

  private final boolean autoCommit;
  // 脏数据标记
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration,
  Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
  ......
}

题外话,有时候我们需要最一些特殊的操作,比如我们根据业务要求要进行大量数据的批量提交,并包含一定的逻辑,这时候我们想获得 Connection,怎么做呢?
这时候我们可以通过 DefaultSqlSession 的 getConnection 方法获取,但是需要注意的是,这个时候我们要控制好事务,这一点尤为重要。

  @Override
  public Connection getConnection() {
    try {
      return executor.getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    }
  }

接下来,我们以其中一个方法为突破口,来分析一下,不失一般性,我们以 insert 为例

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }


  @Override
  public int update(String statement, Object parameter) {
    try {
        // 脏数据标记
      dirty = true;
    //   在SqlSessionFactoty中,我们加载MapperLocation的mapper,
    // 通过parse方法,解析到Configuration对象中。
    // 这里再通过statement这个key从mappedStatements这个map中把它取出来
      MappedStatement ms = configuration.getMappedStatement(statement);
    // 通过DefaultSqlSession的executor执行相应的方法,这里的executor也是一个接口,
    // 在前面openSession中我们介绍了,它根据不同的执行类型创建对应的实现
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

总结:

总体来说,session 的构建还是很简单的,需要注意的是,采用面向接口编程,需要根据不同的参数实例化选择不同实现对象,比较绕。通过 SqlSession 获取 session,session 里包含执行代码需要的其他对象,这些对象根据实际需要构建,最后统一进入到 executor 中去执行。下一节,我们将分析一下 Executor

Mybatis源码笔记(一)、SqlSessionFactory

MyBatis 是基于 Java 的数据持久层框架,主要解决数据和对象的映射关系。相较于 Hibernate,因为其简单轻量,所以使用也比较广泛,是主流的 ORM 框架之一。与 hibernate 相比,mybatis 不能完全算得上是一个完全的 ORM 框架。因为他只完成了一半。不过话说回来,之所以大家喜欢用它,很大一部分原因就是基于它的轻量便捷。

闲言少叙,开刀问斩。首先我们谈一谈传统方式是怎样使用的。传统意义上讲,连接数据库的方式比较常用 JDBC 大致如下:

        ......
        try {
            // 加载驱动
            Class.forName(driver);
            // 获取connection连接
            Connection conn = DriverManager.getConnection(url,user,password);
            // 获取statement对象
            Statement statement = conn.createStatement();
            try {
                // 控制事务提交
                conn.setAutoCommit(false);
                // 执行SQL语句
                statement.executeUpdate("UPDATE USER SET AGE=1 WHERE ID=1");
                // 事务提交
                conn.commit();
            } catch (Exception e) {
                // 异常回滚
                conn.rollback();
                throw e;
            } finally {
                // 关闭statement
                if (statement != null) {
                    statement.close();
                    statement = null;
                }
                // 关闭connection
                if (conn != null) {
                    conn.close();
                    conn = null;
                }
            }

相较于传统的 JDBC 方式,我们使用 mybatis 略有不同,不过本质还是一样使用 JDBC。使用 mybatis 我们不在手动获取 Connection 连接,而是将它交由 mybatis 来管理。在 mybatis 中使用 SqlSession 来管理资源的连接与关闭,同时通过 SqlSession 来进行各种增删改查操作。

具体如何实现呢,我们需要使用 SqlSessionFactory 来管理 SqlSession,SqlSessionFactory 接口主要是用于打开 session 获取 SqlSession 对象的,具体的实现方法在实现类对象中实现


// SqlSessionFactory接口 public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }

那 SqlSession 究竟是如创建的呢?我们以 spring 容器管理为例,通过该 SqlSessionFactoryBean 创建 SqlSessionFactory,通过 SqlSessionFactory 获取 SqlSession:

public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean,
ApplicationListener<ApplicationEvent> {

实现了 InitializingBean 接口,追踪到 afterPropertiesSet()方法

  @Override
  public void afterPropertiesSet() throws Exception {
    ......
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

这里的 sqlSessionFactory 通过 buildSqlSessionFactory 方法创建

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
// configuration不为空,直接加载
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
//   否则通过configLocation加载
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
//   以上均不能加载,则默认new一个Configuration对象塞进去
    } else {
      LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 注册类型别名包
    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
          .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }
// 注册类型别名
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
// 注册各种插件
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
// 注册类型处理包以及类型处理器
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
          .filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }
// 根据databaseIdProvider获取数据库类型
    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
// 注册事务
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));
// 通过mapperLocations加载**.Mapper.xml文件,解析mapper文件存入Configuration对象的属性中
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
// 通过上面的配置创建SqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }
// 这里返回的是SqlSessionFactory默认实现类DefaultSqlSessionFactory,通过SqlSessionFactory我们就可以拿到sqlSession了
    public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

总结:

如果我们自己在单个项目中使用 mybatis 也是很简单的,通过上面的代码可以看到,我们无非就是想获取一个 SqlSessionFactory,通过 SqlSessionFactory 进一步获取 SqlSession,从而执行具体的增删改查。所以我们可以按照上述思路,构建好 Configuration 对象,直接 new 出 SqlSessionFactory。下一节,我们将分析一下 SqlSession。