关于mybatis:mybatis的MappedStatement是线程安全的吗

45次阅读

共计 7207 个字符,预计需要花费 19 分钟才能阅读完成。

本文次要钻研一下 mybatis MappedStatement

MappedStatement

org/apache/ibatis/mapping/MappedStatement.java

public final class MappedStatement {

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
  private boolean dirtySelect;

  //......
}  

MappedStatement 定义了 SqlSource

MappedStatement.Builder

  public static class Builder {private final MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.resultSetType = ResultSetType.DEFAULT;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null,
          new ArrayList<>()).build();
      mappedStatement.resultMaps = new ArrayList<>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}
    //......
  }  

MappedStatement 定义了一个 Builder 用于结构 MappedStatement

MapperBuilderAssistant

org/apache/ibatis/builder/MapperBuilderAssistant.java

public class MapperBuilderAssistant extends BaseBuilder {

  public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
      SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
      String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
      boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
      LanguageDriver lang, String resultSets, boolean dirtySelect) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
        .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
        .resultOrdered(resultOrdered).resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
        .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

  //......
}

MapperBuilderAssistant 定义了 addMappedStatement 来专门用于创立和往 configuration 增加 MappedStatement

Configuration

org/apache/ibatis/session/Configuration.java

public class Configuration {

  protected Environment environment;

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) -> ". please check" + savedValue.getResource() + "and"
              + targetValue.getResource());

  //......

  public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);
  }
  //......
}  

Configuration 则定义了以 statementId 为 key,value 为 MappedStatement 的 StrictMap

Configuration.StrictMap

  protected static class StrictMap<V> extends ConcurrentHashMap<String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;
    private BiFunction<V, V, String> conflictMessageProducer;

    public StrictMap(String name, int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);
      this.name = name;
    }

    public StrictMap(String name, int initialCapacity) {super(initialCapacity);
      this.name = name;
    }

    //......
  }  

StrictMap 继承了 ConcurrentHashMap

SqlSource

org/apache/ibatis/mapping/SqlSource.java

/**
 * Represents the content of a mapped statement read from an XML file or an annotation. It creates the SQL that will be
 * passed to the database out of the input parameter received from the user.
 *
 * @author Clinton Begin
 */
public interface SqlSource {BoundSql getBoundSql(Object parameterObject);

}

而 SqlSource 接口则定义了 getBoundSql 办法,依据入参 parameterObject 来获取 BoundSql
SqlSource 有 DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource 这四种实现

BoundSql

org/apache/ibatis/mapping/BoundSql.java

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;

  //......
}  

BoundSql 则代表了解决动静内容之后的 SQL,该 SQL 可能还蕴含占位符

MappedStatement.getBoundSql

  public BoundSql getBoundSql(Object parameterObject) {BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {String rmId = pm.getResultMapId();
      if (rmId != null) {ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

MappedStatement 的 getBoundSql 办法,在从 sqlSource 获取到的 boundSql 的 parameterMappings 为空时,会依据本人的 ParameterMap 的 getParameterMappings 来从新构建 boundSql

DefaultSqlSession

org/apache/ibatis/session/defaults/DefaultSqlSession.java

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause:" + e, e);
    } finally {ErrorContext.instance().reset();}
  }

DefaultSqlSession 的 selectList 办法则是依据 statement 从 configuration 获取到 MappedStatement 而后传递给 executor

BaseExecutor

org/apache/ibatis/executor/BaseExecutor.java

  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);
  }

BaseExecutor 的 query 从 MappedStatement 获取到了 BoundSql,而后一路传递上来

小结

mybatis 的 MappedStatement 是依据 statementId 从 configuration 获取的,这个是在启动的时候扫描注册下来的,因而如果通过反射改了 MappedStatement 会造成全局的影响,也可能有并发批改的问题;而 BoundSql 则是每次依据 parameter 从 MappedStatement 获取的,而 MappedStatement 则是从 sqlSource 获取到的 BoundSql,因为每次入参都不同,所以这个 BoundSql 是每次执行都会 new 的,因此如果要在拦截器进行 sql 改变,改变 BoundSql 即可。

正文完
 0