MyBatis源码分析

MyBatis源码分析篇章一:入口篇我们学习Mybatis时知道其核心是SqlSessionFactory,它是mybatis的核心类,也是Mybatis运行的入口,spring集成mybatis时需要配置SqlSessionFactoryBean和扫描mapper的MapperScannerConfigurer,spring-mybatis集成主要的配置就这么点,从这理解也就不难理解mybatis入口问题了,但是…

篇章一:入口篇

我们学习Mybatis时知道其核心是SqlSessionFactory,它是mybatis的核心类,也是Mybatis运行的入口,spring集成mybatis时需要配置SqlSessionFactoryBean和扫描mapper的MapperScannerConfigurer,spring-mybatis集成主要的配置就这么点,从这理解也就不难理解mybatis入口问题了,但是<bean>节点只是指明了该类的路径和一些属性信息,并没有指明先运行哪个方法呀?我们带着问题【SqlSessionFactoryBean是入口,那入口方法是哪个?】继续研究下去,

<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations"
			value="classpath:com/jianlejun/mapper/*.xml" />
		<property name="configuration">
			<bean class="org.apache.ibatis.session.Configuration">
				<property name="mapUnderscoreToCamelCase" value="true" />
			</bean>
		</property>
	</bean>
	<!--扫描dao -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.jianlejun.dao" />
		<property name="sqlSessionFactoryBeanName"
			value="sqlSessionFactory"></property>
</bean>

 我们点进去看看SqlSessionFactoryBean.

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

@Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

@Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
  }
}

 可以看到SqlSessionFactoryBean实现了三个接口:

1.ApplicationListener接口:里面只有一个onApplicationEvent方法,有什么用呢?看官方的注释,它是基于观察者模式创建的,当上下文ApplicationContext加载完实现了该接口的bean后,负责通知该bean,bean接收到通知后(onApplicationEvent方法),可以在方法内做自己的逻辑处理,简单点理解就是容器你好,我收到你加载我完毕的通知了,接下里就交给我吧,不用你操心了。

很显然这不是我们要找的方法入口,因为bean都加载完了,说明mybatis配置文件什么都处理完了,那我们再来看看InitializingBean接口

2.InitializingBean接口:接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法(如果bean配置了init-method属性,afterPropertiesSet优先级高于init-method)。

由上可知,这就是我们要找的入口了,spring在初始化bean时,会先调用afterPropertiesSet()方法,从上面代码可知,它在里面调用了buildSqlSessionFactory()方法,哎,最上面我们是不是说了mybatis的核心是SqlSessionFactory,可想而知,这个构造SqlSessionFactory的方法buildSqlSessionFactory()是最核心的方法了。

入口篇完。

篇章二:配置篇

1.配置文件加载

Mybatis配置bean时注入了一个mapperLocations属性,指明项目xml mapper文件的路径,SqlSessionfactoryBean内定义了一个Resource数组接收mapperLocations的位置

<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations"
			value="classpath:com/jianlejun/com/mapper/*.xml" />
		<property name="configuration">
			<bean class="org.apache.ibatis.session.Configuration">
				<property name="mapUnderscoreToCamelCase" value="true" />
			</bean>
		</property>
</bean>
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  private Resource configLocation;

  private Configuration configuration;

  private Resource[] mapperLocations;
/**
   * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory}
   * configuration at runtime.
   *
   * This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file.
   * This property being based on Spring's resource abstraction also allows for specifying
   * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml".
   */
  public void setMapperLocations(Resource[] mapperLocations) {
    this.mapperLocations = mapperLocations;
  }
}

2.读取配置文件

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//省略了前面次要代码
if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
}

这段代码主要根据mapperLocation的文件流构造XMLMapperBuilder(解析xml文件的核心类),然后调用XMLMapperBuilder的parse()方法进行xml文件解析,点进去看看parse()方法体

public class XMLMapperBuilder extends BaseBuilder { 
public void parse() {
    //判断资源文件是否被加载过
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));//解析mapper文件
      configuration.addLoadedResource(resource);//加载解析完毕放入Set容器中
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
}

public class Configuration{

protected final Set<String> loadedResources = new HashSet<String>();

public boolean isResourceLoaded(String resource) {
    return loadedResources.contains(resource);
  }
public void addLoadedResource(String resource) {
    loadedResources.add(resource);
  }
}

来看看ConfigurationElement()方法

public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
    try {
      //获取mapper文件命名空间
      //即<mapper namespace="com.jianlejun.dao.ProductDao">节点的namespace属性值
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析<parameterMap>
      resultMapElements(context.evalNodes("/mapper/resultMap"));//解析<resultMap>节点
      sqlElement(context.evalNodes("/mapper/sql"));//解析<sql>节点
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析CRUD节点
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
}

这里mybatis是将mapper.xml文件分为几个部分来单独解析的,主要关注解析<parameterMap>/<resultMap>/<sql>/<select|..|..>几个步骤

①<resultMap>节点

先上一波源码

public class XMLMapperBuilder extends BaseBuilder {

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //获取<resultMap>标签id属性的值,下面也是解析标签很多属性的值,就不一一注释了
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    //需要映射的实体类(String)
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);//根据全限定名加载类
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    //解析<resultMap>标签的子节点
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();//该方法会调用MapperBuilderAssistant的addResultMap()方法,然后方法内部再调用 configuration.addResultMap(resultMap);将解析好的文件对象存到Configuration
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
}

解析<resultMap>节点时,解析完该节点数据会存储在ResultMap类中,<resultMap>节点是通过id来区分的,那么mybatis底层是如何区分的呢?看源码最终定位到Configuration类的这段代码,当mybatis解析完<resultMap>节点并转换成实体对象ResultMap后,会调用Configuration类的addResultMap方法将解析成功的<resultMap>节点加入到Configuration类,类中定义了一个StrictMap来存储解析成功节点,键为<resultMap>的id(dao类全限定名+id),值为ResultMap对象。但是这个StrictMap比较特殊,它是mybatis自实现的HashMap,并且是Configuration的内部类,map里不能存在相同的key。(这里面试时经常会被问道为什么标签的id为什么不能相同,现在知道怎么回答了吧,其他标签也是如何,最终解析完的结果都是放入都是StrictMap中)

MyBatis源码分析

package org.apache.ibatis.session;
public class Configuration {

protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");

public void addResultMap(ResultMap rm) {

    resultMaps.put(rm.getId(), rm);

    checkLocallyForDiscriminatedNestedResultMaps(rm);

    checkGloballyForDiscriminatedNestedResultMaps(rm);

  }
}

StrictMap源码:


package org.apache.ibatis.session;
public class Configuration {
protected static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;

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

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

    public StrictMap(String name, Map<String, ? extends V> m) {
      super(m);
      this.name = name;
    }

    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key);
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }

    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

    private String getShortName(String key) {
      final String[] keyParts = key.split("\\.");
      return keyParts[keyParts.length - 1];
    }

    protected static class Ambiguity {
      final private String subject;

      public Ambiguity(String subject) {
        this.subject = subject;
      }

      public String getSubject() {
        return subject;
      }
    }
  }
}

至此<resultMap>节点加载解析完毕。

②<select|update|insert|delete>节点

继续来看看ConfigurationElement()方法内部调用的buildStatementFromContext()方法,主要负责解析<select>等CURD标签

public class XMLMapperBuilder extends BaseBuilder {
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
}

里面创建了个XMLStatementBuilder来负责mapper文件<select|update|insert|delete>节点的解析工作,并且调用了其statementParser.parseStatementNode()方法。

具体解析节点xml文件代码如下:

public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    //解析前先解析<include>引用的sql片段
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    #获得sql语句
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}

mybatis在做CURD解析前会调用XMLIncludeTransformer的applyIncludes()方法,将标签内<include>元素解析。

<select>等元素内部的SQL语句会被放入到SqlSource对象中。

最终解析完后会调用MapperBuilderAssistant的addMappedStatement()方法,将<select|insert>等解析完的东东统一封装到MappedStatement中,然后将对象放到Configuration的StrictMap中。

MapperBuilderAssistant类

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) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    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(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

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

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);//最终将解析好的节点信息放入configuration,如果存在相同id的节点则抛出异常
    return statement;
  }

这里关键的是调用configuration.addMappedStatement(statement);跟resultMap节点一样,节点信息也是存入一个StrictMap中

,key为sql语句的id,value为构造好的MapperStatement对象,由于StrictMap是不允许存在相同key的,所以sql的id相同是回抛出一样

Configuration类

package org.apache.ibatis.session;
public class Configuration {

protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
//存储解析好的<select|update|insert|delete>节点信息
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

public boolean hasKeyGenerator(String id) {
    return keyGenerators.containsKey(id);
  }
public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

}

 由下图可以看到key为dao(mapper)全限定名+sql节点的id,value为MappedStatement对象

MyBatis源码分析

其他<parameterMap><sql>节点就不一一赘述了,原理都差不多,<parameterMap>节点最终被封装成ParameterMap对象中,然后放入到StrictMap,<sql>节点主要应用在解析<include>时。

 至此<select|insert|update|delete>节点解析完毕。

篇章三:应用篇

通过上面的章节,我们来思考些问题,mapper文件已经解析完了,那mybatis调用dao的方法时,如何通过dao的方法名找到对应的sql,并最终执行?执行后怎么映射到mapper文件配置的实体上?

先来看看我的service

@Service
@Transactional
public class ProductService {
	@Autowired
	private ProductDao dao;

	public Product findById(Long id) {
		return dao.findById(id);
	}
	public List<ProductDto> findView(Long warehouseId) {
		return dao.findView(warehouseId);
	}
	public List<ProductDto> findView2(Long warehouseId) {
		return dao.findView2(warehouseId);
	}
	public List<ProductDto2> findView3(Long warehouseId) {
		return dao.findView3(warehouseId);
	}
	public List<ProductDto2> findView4(Long warehouseId) {
		return dao.findView4(warehouseId);
	}
}

假设执行的是findView方法

MyBatis源码分析

由上图可以看到这这里注入的dao其实是一个MapperProxy代理,代理最终调用的都是invoke方法(不知道为什么的话请找找谷哥/度娘这对模范夫妻,请教他们是什么代理模式),点进去看看

public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
}

 MyBatis源码分析

 这里先判断代理方法的声明类是否是Object,很显然我这里是ProductDao,不执行if语句块,会转到cachedMapperMethod(),从缓存中获取MapperMethod

MyBatis源码分析

接着执行MapperMethod的execute()

package org.apache.ibatis.binding;
public class MapperMethod {
  private final SqlCommand command;
  private final MethodSignature method;

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //方法参数转换成SQL执行所需要的参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      //最终调用的是SqlSessionTemplate模板类的selectList方法
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
}

execute()方法内部根据带执行的SqlCommand类型匹配switch代码块,我这里的demo是SELECT语句,所以我们直接看 CASE SELECT语句块即可,里面会根据方法放回参数类型类型调用想对应的方法,这里返回的是List,即Many,往下看executeForMany()方法,首先将方法传递的参数转换成执行SQL需要的参数,然后最终会调用SqlSessionTemplate模板的的selectList方法

MyBatis源码分析

 还没完呢MyBatis源码分析,留意到传进selectList的参数并不是SQL语句,说明执行流程还没完,如上图可知这里传递的只是一个String类型的statement串,我们从篇章二的解析过程可以猜测,他最终肯定是要从Configuration对象中根据key来提取MapperStatement对象的?来继续追踪,看看是否验证我们的 猜想。打开SqlSessionTemplate类

 
public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSession sqlSessionProxy;
/**
   * {@inheritDoc}
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
  }
}

哎,这里只是调用了SqlSession接口的selectList,不慌,我们来看看这里注入的SqlSession实现类是哪个

MyBatis源码分析

 哈哈,原来是DefaultSqlSession,继续追踪,要抱着不破楼兰誓不还的决心

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  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();
    }
  }
}

来到这里,终于真相大白了!!!果不其然,最终是从Configuration中获取封装了<select|update|insert|delete>节点信息的MappedStatement对象,获取到了该对象就说明了获取到了待执行的sql语句信息,剩下的如何执行SQL就不是本文该研究的主题了。

 

完结撒花?????????????????

喜欢请轻轻点击下方小拇指

今天的文章MyBatis源码分析分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/6177.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注