欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 Mybatis执行流程三大阶段(源码解析)

程序员文章站 2022-04-15 19:59:50
这里写自定义目录标题一、流程概述二、加载配置的三个核心类三、Configuration对象四、流程解析4.1 初始化阶段parse()解析Mapper.xml1.解析mapper节点1.1解析缓存节点1.2 解析resultMapResultMapResultMapping1.3解析sql标签1.4 解析select、update、delete等标签MappedStatement解析sql语句动态sql判断2.将mapper文件添加到Configuration中3.注册Mapper接口初始化涉及到的设计模式...

一、流程概述

Mybatis的执行流程,首先从入门例子开始:

private SqlSessionFactory sqlSessionFactory;
   @Before
   public void init() throws IOException {
     
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      // 1.读取mybatis配置文件创SqlSessionFactory
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      inputStream.close();
   }
   
   @Test
   public void quickStart() throws IOException {
      // 2.获取sqlSession  
      SqlSession sqlSession = sqlSessionFactory.openSession();
      // 3.获取对应mapper实例
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      //4.调用接口方法
     List<User> users = mapper.selectAll();
    for (User user : users) {
       System.out.println(user);
     }
  }

可以看到,整个流程可以大致分为三个阶段:

  1. 初始化

    读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作;

  2. 代理封装

    封装iBatis模型,使用Mapper接口开发的初始化工作,获取Mapper接口的动态代理对象。

  3. 数据访问

    通过SqlSession完成SQL解析,参数的映射、sql执行、结果解析过程。

二、加载配置的三个核心类

加载配置时,MyBatis是通过建造者模式来实现的(但是只是实现屏蔽了复杂对象的创造过程,并没有建造者熟悉的味道–流式建造过程,更像是工厂模式),三个核心类和相关类图如下:

荐
                                                        Mybatis执行流程三大阶段(源码解析)

其中:

BaseBuilder:是所有解析器的抽象父类,内部维护了配置文件Configuration的唯一实例,提供了通用的方法;

  //初始化过程的核心对象,包含xml配置文件的所有信息,唯一单例
  protected final Configuration configuration;
  //记录TypeAlias别名信息
  protected final TypeAliasRegistry typeAliasRegistry;
  //记录TypeHandler别名信息
  protected final TypeHandlerRegistry typeHandlerRegistry;

XMLConfigBuilder: 主要负责解析 mybatis-config.xml;

XMLMapperBuilder: 主要负责解析映射配置 Mapper.xml 文件;

XMLStatementBuilder: 主要负责解析映射配置文件中的 SQL 节点;

三、Configuration对象

Configuration包含了配置文件中的所有信息,是全局单例的,应用级别生命周期,几个重要属性如下:

public class Configuration {

  /*配置全局性的cache开关,默认为true**/
  protected boolean cacheEnabled = true;


  /* 设置但JDBC类型为空时,某些驱动程序 要指定值**/
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;

  /* 执行类型,有simple、resue及batch**/
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

  /*指定 MyBatis 应如何自动映射列到字段或属性*/
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
 
    //反射工厂
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

  //创建POJO对象工厂
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  /*延迟加载的全局开关*/
  protected boolean lazyLoadingEnabled = false;

  /*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  /*TypeHandler注册中心*/
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();

  /*TypeAlias注册中心*/
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  /*mapper接口的动态代理注册中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  /*mapper文件中增删改查操作的注册中心*/
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

  /*mapper文件中配置cache节点的 二级缓存*/
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");

  /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");

  /*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  /*加载到的所有*mapper.xml文件*/
  protected final Set<String> loadedResources = new HashSet<>();

  /*mapper文件中配置的sql元素,key为命名空间+ID*/
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
}

其中涉及到几个配置相关的类:

MappedStatement(存储 mapper.xml 文件中的 select、insert、update 和 delete 节点和相关属性);

ResultMap:用于解析 mapper.xml 文件中的 resultMap 节点,使用 ResultMapping 封装了我们在resultMap标签中配置的所有属性信息:

public class ResultMapping {

  private Configuration configuration;//引用的configuration对象
  private String property;
  private String column;
  private Class<?> javaType;
  private JdbcType jdbcType;
  private TypeHandler<?> typeHandler;//对应节点的typeHandler属性
  private String nestedResultMapId;////对应节点的resultMap属性,嵌套结果时使用
  private String nestedQueryId;////对应节点的select属性,嵌套查询时使用
  private Set<String> notNullColumns;//对应节点的notNullColumn属性
  private String columnPrefix;
  private List<ResultFlag> flags;//标志,id 或者 constructor
  private List<ResultMapping> composites;
  private String resultSet;//对应节点的resultSet属性
  private String foreignColumn;//对应节点的foreignColumn属性
  private boolean lazy;//对应节点的fetchType属性,是否延迟加载
}

同样的ParameterMap也使用了ParameterMapping维护参数相关信息。

SqlSource:用于创建 BoundSql。mapper.xml 文件中的 sql 语句会被解析成 BoundSql 对象,经过解析 BoundSql 包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行

四、流程解析

4.1 初始化阶段

初始化流程开始的地方是从new SqlSessionFactoryBuilder().build(reader);开始的:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //读取配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

第一个就是XMLConfiBuilder,我们来看下其构造中做了啥:

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

可以看到,其调用父类BaseBuilder的构造,并传入new的一个Configuration对象,这也是唯一一处new Configuration的位置,其他所有地方都是使用该Configuration对象的引用,保证单例。

接着具体看看parser.parse()中是如何解析配置文件的:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

这边首先通过parser.evalNode("/configuration")拿到配置文件的根元素,然后调用parseConfiguration解析:

private void parseConfiguration(XNode root) {
    try {
     //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将settings填充到configuration
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

该方法中就解析了配置文件中所有标签。

我们来重点看解析mappers节点的部分:

在配置文件中我们通常这样定义:

<!-- 映射文件,mapper的配置文件 -->
	<mappers>
		<!--直接映射到相应的mapper文件 -->
		<mapper resource="mapper/UserMapper.xml"/>
		<mapper resource="mapper/OrderMapper.xml" />
	</mappers>

解析:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {//处理mapper子节点
        if ("package".equals(child.getName())) {//package子节点
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {//如果resource不为空
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
            //实例化XMLMapperBuilder解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {//如果url不为空
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
            //实例化XMLMapperBuilder解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {//如果class不为空
            Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
            configuration.addMapper(mapperInterface);//向代理中心注册mapper
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

我们在mappers标签中配置mapper可以有两种方式,一个是配置package让其扫描指定包下的mapper,或者使用mapper标签,通过resource或url或class来指定。

我们重点看一下使用mapper标签配置的方式:

  1. 获取resource、url和class属性的值;

  2. 优先使用resource来解析,创建XMLMapperBuilder,调用parse()解析该xxxMapper.xml文件;

     public void parse() {
    	//判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));//处理mapper节点
          configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
          bindMapperForNamespace();//注册mapper接口
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
      }
    

parse()解析Mapper.xml

1.解析mapper节点

先看下mapper节点一般有啥东西:

<mapper namespace="com.wml.mapper.UserMapper">
    <resultMap id="resultMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="userName"/>
        <result column="password" property="password"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
    </resultMap>

    <sql id="fields">
        id, username, password, name, age
    </sql>

    <select id="userList" resultMap="resultMap">
        SELECT <include refid="fields"/> FROM tb_user
    </select>
    <cache/>
</mapper>

private void configurationElement(XNode context) {
    try {
      //1.获取mapper节点的namespace属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //2.设置到builderAssistant的namespace属性
      builderAssistant.setCurrentNamespace(namespace);
      //3.解析cache-ref节点,可以引用其他命名空间下的缓存 
      cacheRefElement(context.evalNode("cache-ref"));
      //★★★★★★★★4.解析cache节点
      cacheElement(context.evalNode("cache"));
      //5.解析parameterMap节点(已废弃)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //6.★★★★★★★★★★解析resultMap节点(基于数据结果去理解)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6.★★★★★★★★★★★★解析sql节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //7.★★★★★★★解析select、insert、update、delete节点 ----------
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

各种节点的解析中,我们重点看打五角星的。

1.1解析缓存节点

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      //获取cache节点的type属性,默认为PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      //找到type对应的cache接口的实现
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //读取eviction属性,缓存的淘汰策略,默认LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      //根据eviction属性,找到装饰器
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //读取flushInterval属性:缓存的刷新周期
      Long flushInterval = context.getLongAttribute("flushInterval");
      //读取size属性:缓存的容量大小
      Integer size = context.getIntAttribute("size");
     //读取readOnly属性:缓存是否只读
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      //读取blocking属性:缓存是否阻塞
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //通过builderAssistant创建缓存对象,并添加至configuration
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

读取cache中的各个属性后,调用builderAssistantuseNewCache创建缓存对象,并添加至configuration

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
	//建造模式,创建一个cache对象,将cache中的信息注入
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class)) // 缓存实现类默认为PerpetualCache
        .addDecorator(valueOrDefault(evictionClass, LruCache.class)) //缓存策略默认为LRU
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //最后将缓存添加至configuration对象中,注意二级缓存以命名空间为单位进行划分
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

传入Cache信息后调用build()完成Cache的构造:

 public Cache build() {
	  //设置缓存的主实现类为PerpetualCache
    setDefaultImplementations();
    //通过反射实例化PerpetualCache对象
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);//根据cache节点下的<property>信息,初始化cache
    if (PerpetualCache.class.equals(cache.getClass())) {//如果cache是PerpetualCache的实现,则为其添加标准的装饰器
      for (Class<? extends Cache> decorator : decorators) {//为cache对象添加装饰器,这里主要处理缓存清空策略的装饰器
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //通过一些属性为cache对象添加装饰器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      //如果cache不是PerpetualCache的实现,则为其添加日志的能力
      cache = new LoggingCache(cache);
    }
    return cache;
  }

会调用setStandardDecorators为缓存添加装饰器:

private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);//size
      }
      if (clearInterval != null) { //定时清空
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {//读写属性
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);//默认加上日志功能 
      cache = new SynchronizedCache(cache); //默认加上同步能力
      if (blocking) {
          //根据配置加上阻塞能力
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

1.2 解析resultMap

接着看解析resultMap:

private void resultMapElements(List<XNode> list) throws Exception {
	//遍历所有的resultmap节点
    for (XNode resultMapNode : list) {
      try {
    	 //解析具体某一个resultMap节点
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

实际就是解析sql查询的字段与pojo属性之间的转化规则,解析前先分析下resultMap的数据结构:

ResultMap
public class ResultMap {
  private Configuration configuration;//configuration对象

  private String id;//resultMap的id属性
  private Class<?> type;//resultMap的type属性
  private List<ResultMapping> resultMappings;//除discriminator节点之外的映射关系
  private List<ResultMapping> idResultMappings;//记录ID或者<constructor>中idArg的映射关系
  private List<ResultMapping> constructorResultMappings;////记录<constructor>标志的映射关系
  private List<ResultMapping> propertyResultMappings;//记录非<constructor>标志的映射关系
  private Set<String> mappedColumns;//记录所有有映射关系的columns字段
  private Set<String> mappedProperties;//记录所有有映射关系的property字段
  private Discriminator discriminator;//鉴别器,对应discriminator节点
  private boolean hasNestedResultMaps;//是否有嵌套结果映射
  private boolean hasNestedQueries;////是否有嵌套查询
  private Boolean autoMapping;//是否开启了自动映射

以上存储的是和整个resultMap标签的所有信息,而resultMap中的子标签,如constructoridresult等都使用ResultMapping类封装:

ResultMapping
public class ResultMapping {

  private Configuration configuration;//引用的configuration对象
  // ==================== 
  private String property;//节点的property属性
  private String column;//节点的column属性
  private Class<?> javaType;//节点的javaType属性
  private JdbcType jdbcType;//节点的jdbcType属性
  private TypeHandler<?> typeHandler;//节点的typeHandler属性
   //================以上都是子标签可配置的属性信息==============
  private String nestedResultMapId;//节点的resultMap属性,嵌套结果时使用
  private String nestedQueryId;//节点的select属性,嵌套查询时使用
  private Set<String> notNullColumns;//对应节点的notNullColumn属性
  private String columnPrefix;//对应节点的columnPrefix属性
  private List<ResultFlag> flags;//标志,id 或者 constructor
  private List<ResultMapping> composites;
  private String resultSet;//对应节点的resultSet属性
  private String foreignColumn;//对应节点的foreignColumn属性
  private boolean lazy;//对应节点的fetchType属性,是否延迟加载
}

以上两个类都是基于建造者模式构造。

接着看下具体解析过程:

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());
    //获取resultMap节点的type属性
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //获取resultMapp节点的extends属性,描述继承关系
    String extend = resultMapNode.getStringAttribute("extends");
    //获取resultMap节点的autoMapping属性,是否开启自动映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //从别名注册中心获取entity的class对象
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    //记录子节点中的映射结果集合
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    //从xml文件中获取当前resultmap中的所有子节点,并开始遍历
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {//处理<constructor>节点
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {//处理<discriminator>节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {//处理<id> <result> <association> <collection>节点
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
        }
        //创建ResultMapping对象并加入resultMappings集合中
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    //实例化resultMap解析器
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //通过resultMap析器实例化resultMap并将其注册到configuration对象
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

大流程:

  1. 读取resultMap的属性:id、type、extends、autoMapping;
  2. 获取resultMap所有子标签,遍历解析:constructor、discriminator或是id、 result、association、collection,通过创建ResultMapping对象添加到对应集合中;
  3. 最后创建ResultMap解析器,实例化resultMap后添加到Configuration对象中。

创建ResultMapping对象:

 //根据resultmap中的子节点信息,创建resultMapping对象
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //使用建造者模式创建resultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

首先解析出该标签中的所有属性信息,接着通过builderAssistant.buildResultMapping,内部使用ResultMappingBuilder构造一个ResultMapping对象返回;最后将该对象添加到大流程的resultMappings中。

1.3解析sql标签

这部分比较简单,如下:

list为所有的sql标签,遍历后,拿到id属性,然后将id和该节点的映射关系保存到Map结构的sqlFragments中。

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

1.4 解析select、update、delete等标签

同样,先看下维护该标签的数据结构:

MappedStatement

所有的信息都封装到了MappedStatement类中:

public final class MappedStatement {

  private String resource;//节点的完整的id属性,包括命名空间
  private Configuration configuration;
  private String id;//节点的id属性
  private Integer fetchSize;//节点的fetchSize属性,查询数据的条数
  private Integer timeout;//节点的timeout属性,超时时间
  private StatementType statementType;//节点的statementType属性,默认值:StatementType.PREPARED;
  private ResultSetType resultSetType;//节点的resultSetType属性,jdbc知识
  private SqlSource sqlSource;//节点中具体的sql语句信息
  private Cache cache;//对应的二级缓存
  private ParameterMap parameterMap;//已废弃
  private List<ResultMap> resultMaps;//节点的resultMaps属性
  private boolean flushCacheRequired;//节点的flushCache属性是否刷新缓存
  private boolean useCache;//节点的useCache属性是否使用二级缓存
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;//sql语句的类型(枚举类):INSERT, UPDATE, DELETE, SELECT,UNKNOWN
  private KeyGenerator keyGenerator;//节点keyGenerator属性
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;//是否有嵌套resultMap
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;//多结果集使用
}

该类维护了sql语句标签所有的信息,这里里面有几个比较重要的:SqlSource

SqlSource维护了具体的Sql语句信息:

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

内部使用BoundSql封装:

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;
}
  • sql:

    将#{}、${}替换为 ? 后的sql语句

  • parameterMappings

    参数集合,有几个#{}或${}就有几个元素,#{}的位置和该参数集合对应位置映射,如select *from user where id=#{id} and username=#{username},对应集合的第0个元素就是id参数值,第1个元素就是username参数值。

接着看具体解析流程:

 //解析所有的sql语句节点并维护到configuration对象
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //此处创建XMLStatementBuilder 用于解析sql语句节点
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
    	//解析sql语句节点
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

该部分遍历所有的sql语句节点,然后根据当前节点构造一个XMLStatementBuilder对象,用于解析该节点,具体解析如下:

 public void parseStatementNode() {
	//获取sql节点的id
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    /*获取sql节点的各种属性*/
    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);



    //根据sql节点的名称获取sql类型:INSERT, UPDATE, DELETE, SELECT
    String nodeName = context.getNode().getNodeName();
     //SqlCommandType为sql类型的枚举类
    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
    //在解析sql语句之前先解析<include>节点
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    //在解析sql语句之前,先解析<selectKey>节点,随后在xml节点中删除
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //实例化sqlSource,使用sqlSource封装sql语句,这部分较复杂,后面单独讲解
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");//获取resultSets属性
    String keyProperty = context.getStringAttribute("keyProperty");//获取主键信息keyProperty
    String keyColumn = context.getStringAttribute("keyColumn");///获取主键信息keyColumn

    //根据<selectKey>获取对应的SelectKeyGenerator的id
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);


    //获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    //通过builderAssistant实例化MappedStatement,并注册至configuration对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

总体来说还是很简单的,获取对应的属性信息,然后最终调用帮手builderAssistant.addMappedStatement内部通过建造者模式构造一个MappedStatement,最终会添加到Configuration对象的protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");中。

其中,key为命名空间加当前sql标签的id。

解析sql语句

XMLLanguageDriver#createSqlSource

 @Override
  //解析xml文件中的sql语句并封装成SqlSource
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

解析Sql语句时通过XMLScriptBuilder建造器来进行解析的:

//解析sql脚本,返回SqlSource
  public SqlSource parseScriptNode() {
     //1.解析动态sql
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);//动态sql的解析
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);//非动态sql的解析
    }
    //实际返回的都是StaticSqlSource,可以直接让数据库执行的sql语句,包含?占位符
    return sqlSource;
  }

该部分首先进行的是动态sql的判断:

动态sql判断
 protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {//如果是文本类型或者是CDATA类型,则创建TextSqlNode或StaticTextSqlNode
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 如果节点内元素时ELEMENT类型的而不是文本类型
        String nodeName = child.getNode().getNodeName();
          //如果该节点有对应的NodeHandler则为动态sql,否则为静态sql
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
         //处理动态sql
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

这里面有个nodeHandlerMap,该map在构造的时候会调用initNodeHandlerMap

private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

因此,对于和动态sql相关的几个标签,都初始化了对应的Hnadler处理器,即如果能拿到对应的handler,说明该sql是一个动态sql。

接着会调用handler.handleNode(child, contents);处理该动态sql的标签,针对不同标签对应不同的处理类,如上,每个处理类会将sql信息构造成对应的SqlNode节点,添加到contents中:

ifHnandler

 @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
        //将if中test信息封装到IfSqlNode中
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }

这些IfSqlNode的父接口为SqlNode

public interface SqlNode {
  boolean apply(DynamicContext context);
}

其实现类如下:

荐
                                                        Mybatis执行流程三大阶段(源码解析)

该部分用到了组合模式

所有标签节点都实现了apply方法,在DynamicSqlSourcegetBoundSql方法中,如下:

就会调用rootSqlNode.apply(context);方法,从根节点开始,递归的向下调用所有实现SqlNode接口的叶子节点的apply方法。

@Override
  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<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

递归的终节点是TextSqlNode,直接将对应的内容append到sql中:

@Override
  public boolean apply(DynamicContext context) {
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    context.appendSql(parser.parse(text));
    return true;
  }

而其他的节点需要先判断再递归(或传递)向下调用apply方法。

回到前面,动态sql的解析parseDynamicTags结束后,会通过contents构造一个MixedSqlNode返回,contents就是所有SqlNode的集合。

再回到parseScriptNode方法,会根据是否为动态sql,来通过返回的MixedSqlNode创建DynamicSqlSourceRawSqlSource返回。

这样就拿到了最终的SqlSource对象。

OK,到这里解析配置文件中的mapper标签和Mapper.xml的部分就基本讲解完毕了。

接下来继续回到流程解析一开始的parse方法,

 public void parse() {
	//判断是否已经加载该配置文件
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));//处理mapper节点
      configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
      bindMapperForNamespace();//注册mapper接口
    }
    .............
  }

2.将mapper文件添加到Configuration中

configuration.addLoadedResource(resource)会将当前的Mapper.xml文件添加到Configuration对象中的loadedResources中:

/*加载到的所有*mapper.xml文件*/
  protected final Set<String> loadedResources = new HashSet<>();

3.注册Mapper接口

private void bindMapperForNamespace() {
	//获取命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
    	//通过命名空间获取mapper接口的class对象
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {//是否已经注册过该mapper接口?
        
          //将命名空间添加至configuration.loadedResource集合中
          configuration.addLoadedResource("namespace:" + namespace);
          //将mapper接口添加到mapper注册中心
          configuration.addMapper(boundType);
        }
      }
    }
  }

这里会通过命名空间拿到对应的Mapper接口,并将其添加到Configuration对象的Mapper接口的动态代理注册中心里:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
//将mapper接口的工厂类添加到mapper注册中心
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
          throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
      boolean loadCompleted = false;
      try {
    	//实例化Mapper接口的代理工厂类,并将信息添加至knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
       
        //解析接口上的注解信息,并添加至configuration对象
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

knownMappers记录了mapper接口与对应MapperProxyFactory之间的映射关系

以上是初始化的关键流程。

初始化涉及到的设计模式:

  1. 建造者模式

    SqlSessionFactoryBuilder创建SqlSessionFactory

    XMLConfigBuilderXMLMapperBuilderXMLStatementBuilder创建Configuration对象。这几个虽然没有建造者模式的链式编程的feel,但建造者模式的灵魂还是有的。

    CacheBuilder创建二级缓存

    ResultMapping.Builder创建ResultMapping对象

    MappedStatement.Builder创建MappedStatement对象

  2. 组合模式(动态SQL中,不同的SqlNdoe,递归调用apply方法解析)

4.2 代理封装阶段(binding)

4.2.1 SqlSession

SqlSession是对外提供的核心接口,相当于一个门面模式的对外接口,提供了对数据库的读写、获取映射器、管理事物等操作,SqlSessionSqlSessionFactory创建,其默认实现DefaultSqlSession内部维护了一个单例Configuration对象,SqlSession代表一次与数据库的连接。

刚刚提到了SqlSession的默认实现DefaultSqlSession:

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;
}

其内部维护了一个Executor对象,以单一职责为原则,SqlSession对数据库的各种查询、插入、修改等操作最终都交给Executor对象完成。

4.2.2 两种编程方式

 private static SqlSessionFactory ssf;
    private static Reader reader;

    static {
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            ssf = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 public List<User> userList() {
        SqlSession sqlSession=ssf.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.userList();
        return users;
    }

第一种就是通过sqlSession.getMapper(xxMapper.class)拿到对应Mapper接口的实例(此处的UserMapper为动态代理对象),然后调用对应的方法进行处理。

荐
                                                        Mybatis执行流程三大阶段(源码解析)

如上,可以看到代理类是MapperProxy,被代理的接口就是我们的UserMapper接口。

    private static final String NAME_SPACE = "UserMapper.";
public List<User> userList() {
        SqlSession sqlSession=ssf.openSession();
        List<User> users = sqlSession.selectList(NAME_SPACE + "userList");
        return users;
    }

第二种就是直接通过sqlSession.selectList(NAME_SPACE + "userList");传入对应的命名空间+方法id(名)来操作。

其实第一种最终也会转化为第二种进行处理,那么这期间是如何转换的呢?

4.2.3 binding模块

4.2.3.1 getMapper获取代理实例

sqlSession.getMapper(UserMapper.class);开始分析:

进入DefaultSqlSessiongetMapper

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

接着调用configuration对象的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
 protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

这边会涉及到几个关键的类,我们先了解下这几个类的作用:

MapperRegistry

可以看到继续通过mapperRegistry获取,而mapperRegistry前面分析过,是mapper接口和对应的代理对象工厂的注册中心;在4.1第3部分注册Mapper接口中提到,会将当前mapper接口的class对象和对应的代理工厂类添加到knownMappers中:

 public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        .....
      try {
    	//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        .....
      }
    }
  }

knownMappers维护在注册中心MapperRegistry中:

  //记录了mapper接口与对应MapperProxyFactory之间的关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
MapperProxyFactory

用于生成mapper接口动态代理的实例对象;

public class MapperProxyFactory<T> {

  //mapper接口的class对象
  private final Class<T> mapperInterface;
//key是当前mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
}
MapperMethod

封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁

public class MapperMethod {
  //从configuration中获取方法的命名空间.方法名以及SQL语句的类型
  private final SqlCommand command;
  //封装mapper接口方法的信息
  private final MethodSignature method;

SqlCommand:

public static class SqlCommand {
	//sql的名称,命名空间+方法名称
    private final String name;
    //获取sql语句的类型
    private final SqlCommandType type;
}

MethodSignature:

  public static class MethodSignature {

    private final boolean returnsMany;//返回参数是否为集合类型或数组
    private final boolean returnsMap;//返回参数是否为map
    private final boolean returnsVoid;//返回值为空
    private final boolean returnsCursor;//返回值是否为游标类型
    private final boolean returnsOptional;//返回值是否为Optional
    private final Class<?> returnType;//返回值类型
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;//该方法的参数解析器
  }

回到前面的getMapper,调用MapperRegistry.getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

根据当前mapper类型拿到对应的代理工厂后,调用代理工厂的newInstance方法返回代理实例,如下:

 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

可以看到,每次调用都会创建新的MapperProxy代理类对象,(因此当调用多次getMapper(UserMapper.class)返回的实例不是同一个):

protected T newInstance(MapperProxy<T> mapperProxy) {
	//创建实现了mapper接口的动态代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

最后,就是通过动态代理传入相关参数返回被代理接口的代理实例。

到这里,我们就拿到了一开始的UserMapper实例,接着我们调用接口的某个方法时,由于该实例被动态代理,因此,我们需要从代理类MapperProxyinvoke方法中看看如何对mapper接口进行的增强。

4.2.3.2 代理增强

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增强
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用execute方法执行sql
    return mapperMethod.execute(sqlSession, args);
  }

cachedMapperMethod如下:

 private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

调用ConcurrentHashMapcomputeIfAbsent,即如果method不在map的key中,则将method和右边函数的计算结果添加到map中,右侧实例化了一个MapperMethod

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

在以上两个构造中,就会根据configuration对象和另外两个参数初始化SqlCommandMethodSignature类中的属性,如sql的name(命名空间+方法名)、sql类型、接口方法的入参和出参的信息等。

回到invoke方法,最后调用MapperMethodexecute方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据sql语句类型以及接口返回的参数选择调用不同的
    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:
        ....
        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;
  }

到这里就很清晰了,会根据SqlCommandSqlCommandType的值,进入不同的处理逻辑,可以看到增删改部分,解析完方法入参后,就直接调用SqlSession.insert/update/delete方法进行处理,到此就转化为了我们的第二种编程方法。

这里看一下SELECT的处理:

case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {//返回值为void
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//返回值为集合或者数组
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//返回值为map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {//返回值为游标
          result = executeForCursor(sqlSession, args);
        } else {//处理返回为单一对象的情况
          //通过参数解析器解析解析参数
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = OptionalUtil.ofNullable(result);
          }
        }
        break;

可以看到,SELECT中,会根据方法返回值的不同分别处理,其处理逻辑基本一致,都是先解析参数,然后调用对应的sqlsession.select/selectMap/selecList/selectOne方法获得返回值,再将结果值转化为对应的类型返回。

这里以最后一个selectOne,也就是返回值为某个实体类的方法,进行解析:

首先调用MethodSignatureconvertArgsToSqlCommandParam解析参数:

 public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

这里借助参数解析器进行解析,而该解析器在MethodSignature构造的时候进行初始化,内部关键的数据结构为private final SortedMap<Integer, String> names;,该names中存储了当前方法的所有入参的位置和入参名的映射关系。

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        //key:参数名 value:参数具体值
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        //这里主要是添加了参数的通用名称:key: param1 param2... value:参数具体值
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

该方法就是遍历names,将其值,即参数名作为param的key,再通过参数位置从传入的args中拿到对应的参数值,最终返回一个 参数名:参数值 的map结构。

这么处理主要是因为sqlsession.select或者其他方法只接受一个参数,因此原本的参数数组Object[] args无法传入,需要进行转化。

OK,最后一步就是result = sqlSession.selectOne(command.getName(), param);,将SqlCommand的name(即命名空间+方法名)和所需参数传入,就对上了原始的操作方式。

代理封装涉及到的设计模式

动态代理

通过MapperProxyMapper接口进行增强,先通过configuration拿到对应mapper的代理工厂,然后通过动态代理返回mapper接口的代理实例,在增强中调用MapperMethodexecute方法,拿到当前方法的参数和处理的方法名(命名空间+方法名)(相关信息在初始化MapperMethod的时候初始化),最后调用SqlSession对应的方法进行操作。

策略模式

SqlSessionFactory的默认实现DefaultSqlSessionFactoryopenSession方法,会调用openSessionFromDataSource方法,如下:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    	//获取mybatis配置文件中的environment对象
      final Environment environment = configuration.getEnvironment();
      //从environment获取transactionFactory对象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建事务对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据配置创建executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

该方法中,transactionFactory创建事务对象时,会根据mybatis配置文件中的配置的不同,创建JDBC的事务对象或是Managed事务对象,如下:
荐
                                                        Mybatis执行流程三大阶段(源码解析)

我们在配置文件中的配置如下:

 <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"  type="Managed"/>
           .............
        </environment>
    </environments>

4.3 数据读写阶段

该阶段就是处理SqlSession.xxx的阶段,进入默认实现DefaultSqlSession看一下:

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;//是否自动提交事务
  private boolean dirty;//当前缓存是否有脏数据
  private List<Cursor<?>> cursorList;
}

这里有一个核心组件Executor,相关操作最终都借助该组件实现,如:

DefaultSqlsession#select

 @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这里先根据statement拿到对应的MappedStatement对象,statement就是我们传入的命名空间加方法名,MappedStatement就是对应的mapper.xml中该方法的所有信息,在初始化阶段时已经讲过了。

接着调用executor.query()方法处理。

这里我们先了解下 Executor 组件 :

Executor

首先看一下它的类图:

荐
                                                        Mybatis执行流程三大阶段(源码解析)
这里面有两个直接实现类:

BaseExecutor

一个基础抽象类,实现了 executor 接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery,doQueryCursor 等方法;

这里就使用了模板方法模式,即在抽象的BaseExecutor类中定义了方法的执行模板,某些具体的实现交给不同的子类进行实现处理。

先看一下该类的成员属性:

protected Transaction transaction;//事务对象
  protected Executor wrapper;//封装的Executor对象,就是this

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//延迟加载的队列
  protected PerpetualCache localCache;//一级缓存的实现,PerpetualCache
  protected PerpetualCache localOutputParameterCache;//一级缓存用于缓存输出的结果
  protected Configuration configuration;//全局唯一configuration对象的引用

  protected int queryStack;//用于嵌套查询的的层数
  private boolean closed;

再看一下一些查询的基本骨架:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	//获取sql语句信息,包括占位符,参数等信息
    BoundSql boundSql = ms.getBoundSql(parameter);
    //拼装缓存的key值
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

首先通过MappedStatement拿到对应的SQL信息BoundSql,再封装一级缓存值CacheKey

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {//检查当前executor是否关闭
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {//非嵌套查询,并且FlushCache配置为true,则需要清空一级缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;//查询层次加一
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//查询以及缓存
      if (list != null) {
    	 //针对调用存储过程的结果处理
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	 //缓存未命中,从数据库加载数据
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }


    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {//延迟加载处理
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {//如果当前sql的一级缓存配置为STATEMENT,查询完既清空一集缓存
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

这部分很简单,先从一级缓存拿,如果一级缓存为空,就从数据库加载数据,接着做一些延迟加载的处理,最后判断当前sql的一级缓存范围的类型:

主要有两个:

session:就会有数据的共享:

statement:语句范围,这样不会有数据的共享

如果是statement类型的,则查询完就清空一级缓存

调用数据库查询的操作如下:

//真正访问数据库获取结果的方法
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);//在缓存中添加占位符
    try {
      //调用抽象方法doQuery,方法查询数据库并返回结果,可选的实现包括:simple、reuse、batch
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);//在缓存中删除占位符
    }
    localCache.putObject(key, list);//将真正的结果对象添加到一级缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {//如果是调用存储过程
      localOutputParameterCache.putObject(key, parameter);//缓存输出类型结果参数
    }
    return list;
  }

该部分,调用doQuery方法后,就将查询的结果放到一级缓存中返回结果。而该方法就是当前模板类的需要子类实现的方法。

接着看BaseExecutor的三个子类:

SimpleExecutor默认

默认配置,在 doQuery 方法中使用 PrepareStatement 对象访问数据库, 每次访问都要创建新的 PrepareStatement 对象;

可从前面提过的DefaultSqlSessionFactoryopenSessionFromDataSource中看到

//从数据源获取数据库连接
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    ...
      //根据配置创建executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    }....
  }

这里的execType就是从默认的openSession()中传入:

 @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

默认值为:

  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

来看下其doQuery的实现:


  @Override
  //查询的实现
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();//获取configuration对象
      //创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

首先创建了StatementHandler对象,这个是什么作用后面再说,接着调用prepareStatement创建Statement对象,最后调用handler.query方法完成查询。而doUpdate也是最终由handler处理。暂且放一放,先看创建Statement对象的方法:

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取connection对象的动态代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通过不同的StatementHandler,利用connection创建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

首先获取Connection连接对象:

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

这里transaction.getConnection()会从真实数据库中获取连接,如果需要打印日志,则通过动态代理创建一个动态代理的Connection对象返回。

接着又借助handler.prepare()创建了Statement对象,最后又借助handler处理了占位符才返回。

OK,到这里似乎不讲handler不行了,转下面。

BatchExecutor

在 doQuery 方法中,提供批量执行多条 SQL 语句的能力;

ReuseExecutor

SimpleExecutor不同的是,在 doQuery 方法中,使用预编译 PrepareStatement 对象访问数据库,访问时,会重用缓存中的 statement 对象,而不是每次都创建新的PrepareStatement

CachingExecutor

CachingExecutor直接实现Executor接口,实际使用装饰器模式提供缓存能力。先从缓存查,缓存没有再调用delegate.query从数据库查,再将结果添加到缓存中。

在前面SimpleExecutor中,讲创建executor的Configuration#newExecutor方法中,

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   .....
    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);
    }
    //如果有<cache>节点,通过装饰器,添加二级缓存的能力
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
   ...;
    return executor;
  }

如果开启了cache节点,则会创建CachingExecutor

StatementHandler

上面我们发现executor执行的时候会后会借用这个handler进行处理,其作用主要是使用数据库的Statement或PrepareStatement执行操作,包括:创建statement对象,为sql语句绑定参数,执行增删改查等SQL语句、将结果映射集进行转化等功能。

其类图如下:

荐
                                                        Mybatis执行流程三大阶段(源码解析)

BaseStatementHandler

所有子类的抽象父类,使用模板设计模式定义了获取statement的步骤,由子类实现具体的实例化不同的statement(模板模式);

我们可以在每个sql语句标签上配置statement类型:

荐
                                                        Mybatis执行流程三大阶段(源码解析)

不配置,则默认为PREPARED,对应以下三个具体子类:

SimpleStatmentHandler

直接使用statement对象访问数据库,无须参数化;

PreparedStatmentHandler

使用预编译PrepareStatement对象访问数据库;

CallableStatmentHandler

调用存储过程

RoutingStatementHandler:

Excutor组件真正实例化的子类,使用静态代理模式,根据上下文(Mapper中sql标签配置的statement类型)决定创建哪个具体实体类;

OK,回到刚刚的doQuery方法,仔细梳理下:


  @Override
  //查询的实现
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();//获取configuration对象
      //创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

4.3.1.获取Configuration对象

4.3.2.创建StatementHandler对象

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
	StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

这里就是看到,借助RoutingStatementHandler进行构造StatementHandler,如下:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
    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());
    }

  }

其会根据statementType的不同,来创建不同子类的StatementHandler,默认就会创建PreparedStatementHandler,具有预编译功能。

4.3.3.创建Statement

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取connection对象的动态代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通过不同的StatementHandler,利用connection创建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

4.3.3.1 获取connection对象

如果开启日志,则获取其动态代理对象,赋予日志能力;

4.3.3.2 prepare()创建Statement对象

@Override
  //使用模板模式,定义了获取Statement的步骤,其子类实现实例化Statement的具体的方式;
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ......
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置数据集大小
      setFetchSize(statement);
      return statement;
    ....
    }
  }

首先进入抽象的BaseStatementHandler,接着调用instantiateStatement,根据不同的子类进行创建,我们看默认的PreparedStatementHandler

 @Override
  //使用底层的prepareStatement对象来完成对数据库的操作
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    //根据mappedStatement.getKeyGenerator字段,创建prepareStatement
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {//对于insert语句
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
    	//返回数据库生成的主键
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
    	//返回数据库生成的主键填充至keyColumnNames中指定的列
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
     //设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集是否可更新
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      //创建普通的prepareStatement对象
      return connection.prepareStatement(sql);
    }
  }

4.3.3.3 parameterHandler处理占位符

最终会进入默认的参数解析器DefaultParameterHandlersetParameters方法:

参数处理器会对预编译的SQL语句进行参数设置,SQL语句中的占位符“?”替换为具体的值。

 @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //从boundSql中获取sql语句的占位符对应的参数信息
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    //遍历这个参数列表,把参数设置到PreparedStatement中
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {//对于存储过程中的参数不处理
          Object value;//绑定的实参
          String propertyName = parameterMapping.getProperty();//参数的名字
          if (boundSql.hasAdditionalParameter(propertyName)) { // 获取对应的实参值
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取typeHandler对象
          JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的jdbcType
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
        	 //为statment中的占位符绑定参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

这部分总结下,就是遍历parameterMappings即占位符对应的参数信息,然后通过参数名通过不同方式拿到对应参数值,使用typeHandler等,最关键的一步就是typeHandler.setParameter(ps, i + 1, value, jdbcType);,这就和我们使用原生的jdbc操作数据一模一样,将参数值设置到对应的占位符上。

OK到这就拿到了Statement对象,并且处理了占位符。

最后第4步:

调用handler.<E>query进行查询。

进入PreparedStatementHandlerquery方法:

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

首先调用jdbc的execute()方法执行,最后进行结果集的处理。

4.4 结果集处理

此处借助DefaultResultSetHandler对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;

@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;
    //statment可能返回多个结果集对象,这里先取出第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //获取结果集对应resultMap,本质就是获取字段与java属性的映射规则
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);//结果集和resultMap不能为空,为空抛出异常
    while (rsw != null && resultMapCount > resultSetCount) {
     //获取当前结果集对应的resultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);//获取下一个结果集
      cleanUpAfterHandlingResultSet();//清空nestedResultObjects对象
      resultSetCount++;
    }
    //获取多结果集。多结果集一般出现在存储过程的执行,存储过程返回多个resultset,
    //mappedStatement.resultSets属性列出多个结果集的名称,用逗号分割;
    //多结果集的处理不是重点,暂时不分析
    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);
  }
  1. 创建multipleResults集合,用户保存最终返回的结果。

  2. 取出第一个结果集

  3. 获取对应的resultMap

  4. 根据resultMap转化结果集,转换成目标对象后添加到multipleResults集合;

    重点看4这里,一般到这里while循环就结束了。

    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 {
            if (resultHandler == null) {//如果resultHandler为空,实例化一个人默认的resultHandler
              DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
              //对ResultSet进行映射,映射结果暂存在resultHandler中
              handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
              //将暂存在resultHandler中的映射结果,填充到multipleResults
              multipleResults.add(defaultResultHandler.getResultList());
            } else {
              //使用指定的rusultHandler进行转换
              handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
          }
        } finally {
          // issue #228 (close resultsets)
          //调用resultset.close()关闭结果集
          closeResultSet(rsw.getResultSet());
        }
      }
    

    不是嵌套结果集的都进入else部分:

    4.1 创建默认的DefaultResultHandler,内部就是一个暂存结果集的集合:private final List<Object> list;

    4.2 映射resultSet,进入如下方法(无resultMap嵌套的):

     private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
          throws SQLException {
    	//创建结果上下文,暂时缓存结果对象
        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        //1.根据分页信息,定位到指定的记录
        skipRows(rsw.getResultSet(), rowBounds);
        //2.shouldProcessMoreRows判断是否需要映射后续的结果,实际还是翻页处理,避免超过limit
         /*
             private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) 		{
             //检测上下文的stop状态,并检测映射的行数是否达到了limit的上限
            return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
          }
         */
        while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
          //3.进一步完善resultMap信息,主要是处理鉴别器的信息
          ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
          //4.读取resultSet中的一行记录并进行映射,转化并返回目标对象
          Object rowValue = getRowValue(rsw, discriminatedResultMap);
          //5.保存映射结果对象
          storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
      }
    

    这里重点看对单行记录的映射:

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        // 根据resultMap的type属性,实例化目标对象
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
          //对目标对象进行封装得到metaObjcect,为后续的赋值操作做好准备
          final MetaObject metaObject = configuration.newMetaObject(rowValue);
          boolean foundValues = this.useConstructorMappings;//取得是否使用构造函数初始化属性值
          if (shouldApplyAutomaticMappings(resultMap, false)) {//是否使用自动映射
        	 //一般情况下 autoMappingBehavior默认值为PARTIAL,对未明确指定映射规则的字段进行自动映射
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
          }
           // 映射resultMap中明确指定需要映射的列
          foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
          foundValues = lazyLoader.size() > 0 || foundValues;
          //如果没有一个映射成功的属性,则根据<returnInstanceForEmptyRow>的配置返回null或者结果对象
          rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        return rowValue;
      }
    

    4.2-1.1首先根据resultMap中配置的type,通过mybatis的反射模块相关组件反射创建目标对象

    4.2-1.2.将对象封装为MetaObject

    4.2-1.3.对没有明确指明映射规则的属性进行自动映射

    //对未明确指定映射规则的字段进行自动映射
      private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    	//获取resultSet中存在的,但是ResultMap中没有明确映射的列,填充至autoMapping中
        List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
        boolean foundValues = false;
        if (!autoMapping.isEmpty()) {
          //遍历autoMapping,通过自动匹配的方式为属性复制
          for (UnMappedColumnAutoMapping mapping : autoMapping) {
        	//通过typeHandler从resultset中拿值
            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给属性赋值
              metaObject.setValue(mapping.property, value);
            }
          }
        }
        return foundValues;
      }
    

    4.2-1.4对指明规则的属性进行映射:

    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
          throws SQLException {
    	//从resultMap中获取明确需要转换的列名集合
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        //获取ResultMapping集合
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
          String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);//获得列名,注意前缀的处理
          if (propertyMapping.getNestedResultMapId() != null) {
            // the user added a column attribute to a nested result map, ignore it
        	//如果属性通过另外一个resultMap映射,则忽略
            column = null;
          }
          if (propertyMapping.isCompositeResult()//如果是嵌套查询,column={prop1=col1,prop2=col2}
              || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))//基本类型映射
              || propertyMapping.getResultSet() != null) {//嵌套查询的结果
        	//获得属性值
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            // issue #541 make property optional
            //获得属性名称
            final String property = propertyMapping.getProperty();
            if (property == null) {//属性名为空跳出循环
              continue;
            } else if (value == DEFERED) {//属性名为DEFERED,延迟加载的处理
              foundValues = true;
              continue;
            }
            if (value != null) {
              foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
              // gcode issue #377, call setter on nulls (value is not 'found')
              //通过metaObject为目标对象设置属性值
              metaObject.setValue(property, value);
            }
          }
        }
        return foundValues;
      }
    

    4.2-1.5.最后,如果所有属性都映射成功,则返回映射成功的目标对象,否则需根据configuration的returnInstanceForEmptyRow判断是返回null还是目标对象。

    4.2-2:拿到目标对象后,调用storeObject保存该对象。

    private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
        if (parentMapping != null) {//如果是嵌套结果或嵌套查询,将对象保存至父对象
          linkToParents(rs, parentMapping, rowValue);
        } else {//普通映射则把对象保存至resultHandler和resultContext
          callResultHandler(resultHandler, resultContext, rowValue);
        }
      }
    private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
        resultContext.nextResultObject(rowValue);
        ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
      }
    //DefaultResultHandler#handleResult
     public void handleResult(ResultContext<? extends Object> context) {
        list.add(context.getResultObject());
      }
    

    最终将当期目标对象添加到了DefaultResultHandler中的list中。

    如果返回值是一个集合,则会继续4.2 的while循环,最终将所有对象添加到集合中。

  5. 将暂存在DefaultResultHandler的结果添加到最终的multipleResults集合;

  6. resultset.close()关闭结果集

OK到这处理单结果集的流程就结束了,将multipleResults集合返回就拿到了查询的所需结果。

多结果集的不是重点暂不分析。

数据读写阶段涉及的设计模式

1.模板方法

抽象的BaseExecutor编写了查询的基本骨架,doQuery、doUpdate等具体的操作实现交给三个不同子类实现

2.装饰器模式

如果开启了二级缓存,则会创建CachingExecutor,对Executor增强,增加缓存功能。

3.静态代理

RoutingStatementHandler,实现StatementHandler接口,内部持有被代理的StatementHandler对象delegate,根据statementTypedelegate创建不同的子类,接口的所有方法由delegate调用。

另外,mybatis的插件实现,是利用了责任链模式。

本文地址:https://blog.csdn.net/weixin_43696529/article/details/107396049

相关标签: mybatis mybatis