黑马程序员技术交流社区

标题: 【上海校区】Mybatis Mapper.xml 配置文件中 resultMap 节点的源... [打印本页]

作者: 不二晨    时间: 2018-11-23 10:09
标题: 【上海校区】Mybatis Mapper.xml 配置文件中 resultMap 节点的源...
前言
在上篇文章 Mybatis 解析 SQL 源码分析一 介绍了 Maper.xml 配置文件的解析,但是没有解析 resultMap 节点,因为该解析比较复杂,也比较难理解,所有单独拿出来进行解析。
在使用 Mybatis 的时候,都会使用resultMap节点来绑定列与bean属性的对应关系,但是一般就只会使用其简单的属性,他还有一些比较复杂的属性可以实现一些高级的功能,在没查看源码之前,我也只会简单的使用,很多高级的用法都没有使用过,通过这次学习,希望能在工作使用,能够写出简洁高效的SQL。
resultMap的定义
先来看看 resultMap 节点的官方定义:
简单的使用:
<resultMap id="userResultMap" type="User">  <id property="id" column="user_id" />  <result property="username" column="user_name"/>  <result property="password" column="hashed_password"/></resultMap>
会把列名和属性名进行绑定,该节点一共有 4 个属性:
1. id :表示该 resultMap,共其他的语句调用
2. type:表示其对于的pojo类型,可以使用别名,也可以使用全限定类名
3. autoMapping:如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior。默认值为:unset。
4. extends:继承,一个 resultMap 可以继承另一个 resultMap,这个属性是不是没有用过 ? ^^
接下来看下它可以有哪些子节点:
constructor
在查询数据库得到数据后,会把对应列的值赋值给javabean对象对应的属性,默认情况下mybatis会调用实体类的无参构造方法创建一个实体类,然后再给各个属性赋值,如果没有构造方法的时候,可以使用 constructor 节点进行绑定,如现有如下的构造方法:
    public Person(int id, String name, String job, int age) {        this.id = id;        this.name = name;        this.job = job;        this.age = age;    }
则,可以使用 constructor  节点进行绑定:
    <resultMap id="queryPersonMap" type="mybatis.pojo.Person" >        <constructor>            <idArg column="id" javaType="int"/>            <arg column="name" javaType="string" />            <arg column="job" javaType="string" />            <arg column="age" javaType="int" />        </constructor>    </resultMap>association
关联查询,在级联中有一对一、一对多、多对多等关系,association主要是用来解决一对一关系的,association 可以有多种使用方式:
比如现在有一个 Person 类,它有一个 Address 属性,关联 Address 对象:
public class Person implements Serializable {    private int id;    private String name;    private String job;    private int age;    private Address address;}public class Address {    private int id;    private String name;    private long number;}
关联查询方式一:
    <resultMap id="queryPersonMap" type="mybatis.pojo.Person" >        <id column="id" property="id"/>        <result column="name" property="name" />        <result column="job" property="job" />        <result column="age" property="age"/>        <association property="address" column="address_id" javaType="mybatis.pojo.Address" select="queryAddress" />    </resultMap>    <select id="queryAddress" resultType="mybatis.pojo.Address">        select * from address where id = #{id}    </select>
关联查询方式二:
    <resultMap id="queryPersonMap" type="mybatis.pojo.Person" >        <id column="id" property="id"/>        <result column="name" property="name" />        <result column="job" property="job" />        <result column="age" property="age"/>        <association property="address" column="address_id" javaType="mybatis.pojo.Address" resultMap="addressMap"/>    </resultMap>        <resultMap id="addressMap" type="mybatis.pojo.Address">        <id column="id" property="id"/>        <result column="name" property="name"/>        <result column="number" property="number"/>    </resultMap>
关联查询方式三:
    <resultMap id="queryPersonMap" type="mybatis.pojo.Person" >        <id column="id" property="id"/>        <result column="name" property="name" />        <result column="job" property="job" />        <result column="age" property="age"/>        <association property="address" javaType="mybatis.pojo.Address">            <id column="id" property="id"/>            <result column="name" property="name"/>            <result column="number" property="number"/>        </association>    </resultMap>collection
collection 集合,如果pojo对象有一个属性是集合类型的,可以使用collection 来进行查询:
public class Person implements Serializable {    private int id;    private String name;    private String job;    private int age;    private List<Address> addressList;}    <resultMap id="queryPersonMap" type="mybatis.pojo.Person" >        <id column="id" property="id"/>        <result column="name" property="name" />        <result column="job" property="job" />        <result column="age" property="age"/>        <collection property="addressList" javaType="ArrayList" ofType="mybatis.pojo.Address">            <id column="id" property="id"/>            <result column="name" property="name"/>            <result column="number" property="number"/>        </collection>    </resultMap>
当然还有其他的方法,具体可以参考官网。
discriminator
鉴别器,mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为,有点像 Java的 switch 语句,鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型,
比如某列的值等于多少,则返回1,等于多少返回2等等。
<resultMap id="vehicleResult" type="Vehicle">  <id property="id" column="id" />  <result property="vin" column="vin"/>  <result property="year" column="year"/>  <result property="make" column="make"/>  <result property="model" column="model"/>  <result property="color" column="color"/>  <discriminator javaType="int" column="vehicle_type">    <case value="1" resultMap="carResult"/>    <case value="2" resultMap="truckResult"/>    <case value="3" resultMap="vanResult"/>    <case value="4" resultMap="suvResult"/>  </discriminator></resultMap>
以上就是 resultMap 节点的全部使用方法,下面是一个比较复杂的例子,源码解析会按照其来解析,例子来自于官方文档。
<resultMap id="detailedBlogResultMap" type="Blog">  <constructor>    <idArg column="blog_id" javaType="int"/>    <arg column="name" javaType="string" />  </constructor>  <id column="id" property="id" />  <result property="title" column="blog_title"/>  <association property="author" javaType="Author">    <id property="id" column="author_id"/>    <result property="username" column="author_username"/>  </association>  <collection property="posts" ofType="Post">    <id property="id" column="post_id"/>    <result property="subject" column="post_subject"/>  </collection>    <discriminator javaType="int" column="draft">    <case value="1" resultType="DraftPost"/>  </discriminator></resultMap>resultMap 源码解析
首先需要说明的是,一个 resultMap 节点会解析成一个 ResultMap 对象,而每个子节点(除了discriminator节点)会被解析成 ResultMapping 对象,即一个 ResultMap 包含的是 ResultMapping 对象的集合。
先来看看 ResultMapping 的一个声明:
public class ResultMapping {  // configuration 对象  private Configuration configuration;  private String property;  private String column;  private Class<?> javaType;  private JdbcType jdbcType;  private TypeHandler<?> typeHandler;  // 对应的是 resultMap 属性,通过id来引用其他的resultMap  private String nestedResultMapId;  // 对应的是 select 属性,通过id来引用其他的select节点的定义  private String nestedQueryId;  private Set<String> notNullColumns;  private String columnPrefix;  // 处理后的标志,标志有两个 id和constructor  private List<ResultFlag> flags;  // 对应节点的column属性拆分后生成的结果,composites.size()>0会使column为null  private List<ResultMapping> composites;  private String resultSet;  private String foreignColumn;  private boolean lazy;|
ResultMap 的声明如下:
public class ResultMap {  // ID,表示一个resultMap  private String id;  // 该resultMap对应的Javabean类型  private Class<?> type;  // 对应的是除了discriminator节点外的其他节点  private List<ResultMapping> resultMappings;  // id 节点的映射集合  private List<ResultMapping> idResultMappings;  // 构造节点的集合  private List<ResultMapping> constructorResultMappings;  // 记录了映射关系中 不带有contructot节点的的映射关系  private List<ResultMapping> propertyResultMappings;  // column集合  private Set<String> mappedColumns;  // discriminator 节点  private Discriminator discriminator;  private boolean hasNestedResultMaps;  private boolean hasNestedQueries;  private Boolean autoMapping;}
解析:
  resultMapElements(context.evalNodes("/mapper/resultMap"));  private void resultMapElements(List<XNode> list) throws Exception {    for (XNode resultMapNode : list) {      try {        // 解析每个 resultMap 节点        resultMapElement(resultMapNode);      } catch (IncompleteElementException e) {        // ignore, it will be retried      }    }  }  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {    // 注意这里传入的是一个空的集合    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());  }
主要的解析方法:
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());    // ID 属性    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());    // type属性    String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",        resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));    // extends 属性    String extend = resultMapNode.getStringAttribute("extends");    // autoMapping 属性    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");    // 从注册的类型管理器里面查找对应的类型    Class<?> typeClass = resolveClass(type);    // discriminator 节点    Discriminator discriminator = null;    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();    resultMappings.addAll(additionalResultMappings);    // 处理子节点    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 {        List<ResultFlag> flags = new ArrayList<ResultFlag>();        if ("id".equals(resultChild.getName())) {          flags.add(ResultFlag.ID);        }        // 处理其他节点,创建 resultMapping 对象并添加到集合中        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));      }    }    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);    try {      // 创建代表该 resultMap 节点的 ResultMap 对象并添加到 ResultMap 集合中。      return resultMapResolver.resolve();    } catch (IncompleteElementException  e) {      // 解析失败,添加到集合,重新解析      configuration.addIncompleteResultMap(resultMapResolver);      throw e;    }  }
处理 constructor 节点:
  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {    List<XNode> argChildren = resultChild.getChildren();    for (XNode argChild : argChildren) {      List<ResultFlag> flags = new ArrayList<ResultFlag>();      // 向集合中添加 contrucator 标志      flags.add(ResultFlag.CONSTRUCTOR);      if ("idArg".equals(argChild.getName())) {        // 添加id标志        flags.add(ResultFlag.ID);      }      // 创建 ResultMapping 对象并添加到集合中      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));    }  }
创建 ResultMapping 对象:
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {    // 解析节点的属性    String 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")    // 对应的 typeHandler 类型    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);  }
之后是创建 ResultMapped 对象并添加到集合中:
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);// 调用的使用 builderAssistant 的 addResultMap 方法return resultMapResolver.resolve();  public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {    // 为 id 加上 namespace即 namespace.id    id = applyCurrentNamespace(id, false);    extend = applyCurrentNamespace(extend, true);    if (extend != null) {      if (!configuration.hasResultMap(extend)) {        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");      }       // 获取父级的resultMap      ResultMap resultMap = configuration.getResultMap(extend);      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());      // 因为上面添加过一次,现在要删除重复的      extendedResultMappings.removeAll(resultMappings);      // Remove parent constructor if this resultMap declares a constructor.      boolean declaresConstructor = false;      for (ResultMapping resultMapping : resultMappings) {        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {          declaresConstructor = true;          break;        }      }      if (declaresConstructor) {        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();        while (extendedResultMappingsIter.hasNext()) {          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {            extendedResultMappingsIter.remove();          }        }      }      resultMappings.addAll(extendedResultMappings);    }    // 创建 resultMap     ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)        .discriminator(discriminator)        .build();    // 添加到集合    configuration.addResultMap(resultMap);    return resultMap;  }
到这里,就把 resultMap 节点解析完毕了,之后在解析 Mapper.xml 文件的其他节点,参考 Mybatis 解析 SQL 源码分析一


作者: 小影姐姐    时间: 2018-11-26 14:13

作者: 不二晨    时间: 2018-11-28 15:43
奈斯




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2