当前位置: 移动技术网 > IT编程>开发语言>Java > 荐 Mybatis执行流程三大阶段(源码解析)

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

2020年07月17日  | 移动技术网IT编程  | 我要评论
这里写自定义目录标题一、流程概述二、加载配置的三个核心类三、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是通过建造者模式来实现的(但是只是实现屏蔽了复杂对象的创造过程,并没有建造者熟悉的味道–流式建造过程,更像是工厂模式),三个核心类和相关类图如下:

在这里插入图片描述

其中:

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

其实现类如下:

在这里插入图片描述

该部分用到了组合模式

所有标签节点都实现了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为动态代理对象),然后调用对应的方法进行处理。

在这里插入图片描述

如上,可以看到代理类是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事务对象,如下:
在这里插入图片描述

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

 <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

首先看一下它的类图:

在这里插入图片描述
这里面有两个直接实现类:

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语句、将结果映射集进行转化等功能。

其类图如下:

在这里插入图片描述

BaseStatementHandler

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

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

在这里插入图片描述

不配置,则默认为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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网