本文次要钻研一下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即可。