【Mybatis源码分析】06-SqlSession执行过程之获取BoundSql代理对象

上篇文章分析Mapper的查询操作最终都会调用SqlSession的selectList方法,接下来几篇文章分析一下DefaultSqlSession的selectList的执行过程。

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

首先调用DefaultSqlSession内的executor对象,如果开启了二级缓存executor就是类CachingExecutor的实例,然后调用executor的query方法其中第二个参数为我们在上篇文章提到过被转换的参数对象可能是null,ParamMap或一个对象,如果这个对象是Collection或数组则会再将这个参数对象转换为一个Map对象。

private Object wrapCollection(final Object object) {
  if (object instanceof Collection) {
    StrictMap map = new StrictMap();
    map.put("collection", object);
    if (object instanceof List) {
      map.put("list", object);
    }
    return map;
  } else if (object != null && object.getClass().isArray()) {
    StrictMap map = new StrictMap();
    map.put("array", object);
    return map;
  }
  return object;
} 
  

回过头来再看CachingExecutor的query方法:

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

由于这个query方法比较复杂,这篇我先分析获取BoundSql对象也就是MappedStatement的getBoundSql方法。BoundSql内包含了执行的sql语句和命名参数。

public class BoundSql {

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

我画了一副时序图来说明获取BoundSql的过程。

【Mybatis源码分析】06-SqlSession执行过程之获取BoundSql代理对象_第1张图片

下面按时序图的步骤查看源代码。

1.mapperStatement的getBoundSql方法会调用其成员变量sqlSource的getBoundSql方法

public BoundSql getBoundSql(Object parameterObject) {
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  for (Map.Entry, Object> entry : context.getBindings().entrySet()) {
    boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
  }
  return boundSql;
}

DynamicSqlSource会创建一个DynamicContext对象,这个对象作为SqlNode的apply方法的入参,SqlNode每调用一次apply方法,就是将此节点含有的sql片段拼接到context中去,此外还包含了参数对象,通过MetaObject(本文最后介绍)可以使用A.B[0].C的形式

2.DynamicSqlSource所持有的SqlNode为MixedSqlNode,它内部的contents对象就是顶级节点下的兄弟sql片段,依次调用兄弟片段的apply方法拼接sql,当然其中每个兄弟sql片段下还有可能为多个sql片段。假如一个sql片段是一个不含任何可变参数的那么它就是StaticTextSqlNode,静态节点就是单纯的拼接一下sql,当然还有其他种类的SqlNode,譬如WhereSqlNode,看过它的代码我们就可以看出对于where后第一个and或or是不会拼入sql中的,我们这里就不一一列举了。

public boolean apply(DynamicContext context) {
  for (SqlNode sqlNode : contents) {//MixedSqlNode
    sqlNode.apply(context);
  }
  return true;
}
public boolean apply(DynamicContext context) {//StaticTextSqlNode
  context.appendSql(text);
  return true;
}

3.此时context中已经含有完整的sql语句了只不过还没有替换掉mybatis的变量占位符信息(类似#{prop,jdbcType=...})。下一步通过SqlSourceBuilder的parse方法将sql语句转换为标准的带有Jdbc站位符?的语句,并将变量占位符信息抽像为ParameterMapping。返回一个包含sql和List的StaticSqlSource。

public SqlSource parse(String originalSql, Class parameterType, Map, Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql = parser.parse(originalSql);
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

GenericTokenParser将#{}中的内容过滤出来,使用ParameterMappingTokenHandler将其转换为ParameterMapping对象,然后用?将#{}替换。

4.通过以上步骤将一个DynamicSqlSource转换成了一个含有List的StaticSqlSource,调用getBoundSql方法返回BoundSql对象。

public BoundSql getBoundSql(Object parameterObject) {
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
MetaObject介绍

上面第一步中说到MetaObject可以将参数对象对外提供A.B[0].C的访问形式,那么他是怎么做到的呢。

Configuration的newMetaObject方法调用MetaObject.forObject()方法将自身持有的DefaultObjectFactory,DefaultObjectWrapperFactory和DefaultReflectorFactory一并传入。

public MetaObject newMetaObject(Object object) {
  return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  if (object == null) {
    return SystemMetaObject.NULL_META_OBJECT;
  } else {
    return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
  }
}
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  this.originalObject = object;
  this.objectFactory = objectFactory;
  this.objectWrapperFactory = objectWrapperFactory;
  this.reflectorFactory = reflectorFactory;

  if (object instanceof ObjectWrapper) {
    this.objectWrapper = (ObjectWrapper) object;
  } else if (objectWrapperFactory.hasWrapperFor(object)) {
    this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  } else if (object instanceof Map) {
    this.objectWrapper = new MapWrapper(this, (Map) object);
  } else if (object instanceof Collection) {
    this.objectWrapper = new CollectionWrapper(this, (Collection) object);
  } else {
    this.objectWrapper = new BeanWrapper(this, object);
  }
}

然后MetaObject通过构造方法将这些参数保留下来,构造函数对不同的object类型创建不同的ObjectWrapper对象,MetaObject通过ObjectWrapper对object提供了方便的多层级的getter和setter方法。

【Mybatis源码分析】06-SqlSession执行过程之获取BoundSql代理对象_第2张图片 【Mybatis源码分析】06-SqlSession执行过程之获取BoundSql代理对象_第3张图片

MetaObject对外提供属性的访问是getValue方法:

public Object getValue(String name) {
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      return null;
    } else {
      return metaValue.getValue(prop.getChildren());
    }
  } else {
    return objectWrapper.get(prop);
  }
}
 
  
public MetaObject metaObjectForProperty(String name) {
  Object value = getValue(name);
  return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}

1.根据PropertyTokenizer判断是否是一个多层级的属性访问,如果是则先取顶层对象将其重新包裹为一个MetaObject对象,然后对这个对象再次调用getValue方法,这个时候getValue的参数为去除第一层级的字符串了,直到入参name为单层级的字符串也是就不带"."的字符串然后调用objectWrapper的get方法取出name所对应的属性值。这里我们以BeanWrapper为例看看这个方法的实现。

public Object get(PropertyTokenizer prop) {
  if (prop.getIndex() != null) {
    Object collection = resolveCollection(prop, object);
    return getCollectionValue(prop, collection);
  } else {
    return getBeanProperty(prop, object);
  }
}
首先判断当前访问的属性是否含有索引值也即是collection[key]这种形式,如果有则先得到这个集合然后在对集合(或Map或数组)按索引取值,如果不是类集合类型则使用metaClass使用getGetInvoker调用对象的getter方法取值。MetaClass内部使用的是反射技术,有兴趣的可以看看这个类的实现。

你可能感兴趣的:(Mybatis源码分析)