精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)

前言

这段时间小编工作较忙,然后好久没更新博客了,上次小编在博客精通Mybatis之Jdbc处理器StatementHandler还落下了对结果集封装处理以及MetaObject反射工具的详细讲解。结果集的封装映射比较复杂,一次肯定讲解不完,小编打算分三次讲完。首先我们来回顾一下查询后结果集处理流程,再次查看其源码中的步骤,然后讲解一下MetaObject和一部分映射体系。

结果集处理流程

通过StatementHandler执行sql查询到结果集后,如下图处理:
精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)_第1张图片
resultSetHandler由一条或多行数据库的行记录,然后一条一条转换成对应的Object(根据映射),放入ResultContext,最后根据ResultHandler处理放入到其list中。那为什么不直接放入ResultHandler的list中,原因是ResultContext这个结果集上下文组件可以控制解析数量以及状态,也就是是否需要继续解析前面的结果集行。
小编用代码来解释一下ResultContext的作用:

@Test
    public void ResultContextTest(){
     
        final List<Object> resultList = new ArrayList<Object>();
        ResultHandler resultHandler = new ResultHandler() {
     
            public void handleResult(ResultContext resultContext) {
     
                int resultCount = resultContext.getResultCount();
                if(resultCount>2){
     
                    resultContext.stop();
                }
                Object resultObject = resultContext.getResultObject();
                resultList.add(resultObject);
            }
        };
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
        sqlSession.select("mapper.EmployeeMapper.selectAll",resultHandler);
        System.out.println(resultList.size());
    }

结果:

Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@47d90b9e]
==>  Preparing: select * from employee 
==> Parameters: 
<==    Columns: id, name
<==        Row: 1, wangwu
<==        Row: 2, zhaoliu
<==        Row: 3, tom
3

数据库值:
精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)_第2张图片

结果集查询后转换流程

当执行器执行完sql拿出结果集后需要经历一系列过程才能将数据库表中的行数据变成我们需要的java对象。小编用图给大家说明一下:
精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)_第3张图片
上图所示,可能有点杂乱,其实所有的操作都在DefaultResultSetHandler类,这基本是mybatis框架中代码最多的类,大家如果把这个类理顺了,基本上就对结果集处理流程全部了若指掌。小编稍微理了一下顺序,必要时源码解读。

  1. 首先获取结果集方法getFirstResultSet(stmt)并封装成ResultSetWrapper对象
  2. 校验完是否有结果集后,就处理结果集,进行遍历,一行一行处理
  3. 行数据处理时分为两种情况(上图为简单情况),一种为有嵌套的结果集映射,第二种为单个简单结果集
  4. 处理简单行数据时,首先判断是否需要跳转到某一行,跳转行有两种方法,一个是数据库本身就支持跳到哪一行,第二种就是空遍历遍历到需要的行为止。
  5. 然后根据resultContext是否过滤和关闭来进行单行数据处理。
  6. 之后有这样一个代码ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);这个不需要管,跳过直接到getRowValue方法
  7. 将行数据转换成object对象,先根据类型创建对象(为空对象),上图说明了。
  8. 创建为对象后填充属性,还是有两种其情况:a、自动映射创建属性 (配置autoMapping=true) b、手动映射。自动映射比较简单,根据类的名称自动对应,手动映射难一点因为还会调用一个方法。
  9. 手动映射为什么需要有一个获取属性值的方法?这是因为手动映射的时候可能会涉及到嵌套查询,即对象的值是另一个需要查询的对象。(存储过程的多结果集忽略)
    10.设置属性值,然后封装完毕对象后,暂存到resultContext 最后放入ResultHandler。

上面的流程还没涉及嵌套查询和结果集映射(一对多,多对多等等),那小编接着往下讲解。

映射体系

结果集映射之前,先了解一下MetaObject反射工具,这个比较重要,这个工具在封装属性值和封装结果值的时候都需要用到,小编觉得大家在封装相应的工具类的时候都可以借鉴,而且这个类相对是底层的,不依赖其他的类,有时候可以直接使用。废话不多说,进入正题。

MetaObject反射工具

当结果集中的列填充至JAVA Bean属性。这就必须用到反射,而Bean的属性多种多样,有普通属性、对象、集合、Map都有可能。为了更加方便的操作Bean的属性,MyBatis提供了MeataObject 工具类,其简化了对象属性的操作。其具体功能如下:

  1. 查找属性:勿略大小写,支持驼峰、支持子属性 如:“company.department.employee_name”
  2. 获取属性
    基于点获取子属性 “company.name”
    基于索引获取列表值 “department[0].id”
    基于key获取map值 “user[name]”
  3. 设置属性:
    可设置子属性值
    支持自动创建子属性(必须带有空参构造方法,且不能是集合)

代码示例:
三个类:分别为公司,部门和员工

@Data
public class Company {
     

    private Long id;

    private String companyName;

    private List<Department> departmentList;

}

@Data
public class Department {
     

    private Long id;

    private String departmentName;

    private List<Employee> employeeList;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
     

    private Long id;

    private String name;

    private Department department;
    
    private Company company;

}

测试代码:

public class MetaObjectTest {
     

    @Test
    public void reflectTest(){
     
        Company company = new Company();
        Configuration configuration = new Configuration();
        MetaObject metaObject = configuration.newMetaObject(company);
        //设置公司名称值
        metaObject.setValue("companyName","I'm your Boss");
        Object companyName = metaObject.getValue("companyName");
        //获取属性
        System.out.println(companyName);
    }

    @Test
    public void reflectTest1(){
     
        Employee employee = new Employee();
        Configuration configuration = new Configuration();
        MetaObject metaObject = configuration.newMetaObject(employee);
        //employee的department本身是空的可以直接赋值
        metaObject.setValue("department.departmentName","finance department ");
        Object departmentName = metaObject.getValue("department.departmentName");
        //获取属性以及本身的空对象
        System.out.println(departmentName);
        System.out.println(employee.getDepartment());
    }

    @Test
    public void reflectTest2(){
     
        Employee employee = new Employee();
        Configuration configuration = new Configuration();
        MetaObject metaObject = configuration.newMetaObject(employee);
        //employee的department本身是空的可以直接赋值以及驼峰命名
        String property = metaObject.findProperty("department.department_name", true);
        //查找驼峰命名的属性名称
        System.out.println(property);
        metaObject.setValue(property,"finance department ");
        Object departmentName = metaObject.getValue(property);
        //获取属性以及本身的空对象
        System.out.println(departmentName);
        System.out.println(employee.getDepartment());
    }

    @Test
    public void reflectTest3(){
     
        Company company = new Company();
        Configuration configuration = new Configuration();
        MetaObject metaObject = configuration.newMetaObject(company);
        //因为结合metaObject不能直接生成
        Department department = new Department();
        department.setDepartmentName("technical division");
        List<Department> departments = Collections.singletonList(department);
        metaObject.setValue("departmentList",departments);
        //获取属性
        System.out.println(metaObject.getValue("departmentList[0].departmentName"));
        //还可以继续设置属性,不停的拿出属性
        Employee employee = new Employee();
        employee.setName("Bob");
        department.setEmployeeList(Collections.singletonList(employee));
        System.out.println(metaObject.getValue("departmentList[0].employeeList[0].name"));
    }


}

大家有空可以根据代码直接去调试一下。接下来看下MetaObject的结构

MetaObject结构

精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)_第4张图片

  • BeanWrapper: 功能与MeataObject类似,不同点是BeanWrapper只针对单个当前对象属性进行操作,不能操作子属性。
  • MetaClass :类的反射功能支持,获能获取整完整类的属性,包括属性的属性。
  • Reflector :类的反射功能支持,仅支持当前类的属性。

MetaObject流程

小编以上面示例代码为例讲一下MetaObject获取值的流程。
如:"employeeList[0].department.name"这个取值比较麻烦的就这个吧。
代码如下:

@Test
    public void reflectTest4(){
     
        Department department = new Department();
        department.setDepartmentName("technical division");
        //还可以继续设置属性,不停的拿出属性
        Employee employee = new Employee();
        employee.setName("Bob");
        department.setEmployeeList(Collections.singletonList(employee));
        employee.setDepartment(department);
        Configuration configuration = new Configuration();
        MetaObject metaObject = configuration.newMetaObject(department);
        System.out.println(metaObject.getValue("employeeList[0].department.departmentName"));
    }

流程图:
精通Mybatis之结果集处理流程与映射体系(MetaObJect工具以及嵌套子查询)(一)_第5张图片

流程中方法说明:
MeataObject.getValue()
获取值的入口,首先根据属性名"employeeList[0].department.name" 解析成PropertyTokenizer,并基于属性中的“.” 来判断是否为子属性值,如果是就递归调用getValue()获取子属性对象。然后在递归调用getValue()获取子属性下的属性。直到最后的name属性获取成功。上图并不完整啊。留给大家自己探索。

MeataObject.setValue()
流程与getValue()类似,不同在于如果子属性不存在,则会尝试创建子属性。这个留给大家自己调试和阅读代码。

MetaObject源码

getValue方法

public Object getValue(String name) {
     
	//分割字符串名称
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
     
    //有子属性 然后先取出最上面的一个employeeList[0]
    //如果是employeeList先取出结果集然后再根据索引取出对应的值
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
     
        return null;
      } else {
     
      	//继续取子属性
        return metaValue.getValue(prop.getChildren());
      }
    } else {
     
     //通过反射取值
     //第一次是取
      return objectWrapper.get(prop);
    }
  }

PropertyTokenizer 分割名称

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
     
  private String name;
  private final String indexedName;
  private String index;
  private final String children;

  public PropertyTokenizer(String fullname) {
     
  	//以.为分割并取到最近的一个下标
    int delim = fullname.indexOf('.');
    if (delim > -1) {
     
      //不为空则分割第一个名称上面则为employeeList[0]
      name = fullname.substring(0, delim);
      //children为department.name
      children = fullname.substring(delim + 1);
    } else {
     
     //没有则直接取出,children为null
      name = fullname;
      children = null;
    }
    //indexName 与名称相同 employeeList[0]
    indexedName = name;
    //如果是数组列表
    delim = name.indexOf('[');
    
    if (delim > -1) {
     
    //取出index的值上面就是0
      index = name.substring(delim + 1, name.length() - 1);
      //获取名称employeeList
      name = name.substring(0, delim);
    }
  }
@Override
  public boolean hasNext() {
     
    return children != null;
  }

  @Override
  public PropertyTokenizer next() {
     
    return new PropertyTokenizer(children);
  }

metaObjectForProperty方法

public MetaObject metaObjectForProperty(String name) {
     
    Object value = getValue(name);
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
  }

BaseWrapper类

 @Override
  public Object get(PropertyTokenizer prop) {
     
    if (prop.getIndex() != null) {
     
     //获取集合的值
      Object collection = resolveCollection(prop, object);
      //获取集合对应的索引值
      return getCollectionValue(prop, collection);
    } else {
     
     //不是集合的话直接根据反射取值
      return getBeanProperty(prop, object);
    }
  }
protected Object resolveCollection(PropertyTokenizer prop, Object object) {
     
    if ("".equals(prop.getName())) {
     
      return object;
    } else {
     
      return metaObject.getValue(prop.getName());
    }
  }
 //集合类取值方法
protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
     
    if (collection instanceof Map) {
     
      return ((Map) collection).get(prop.getIndex());
    } else {
     
      int i = Integer.parseInt(prop.getIndex());
      if (collection instanceof List) {
     
        return ((List) collection).get(i);
      } else if (collection instanceof Object[]) {
     
        return ((Object[]) collection)[i];
      } else if (collection instanceof char[]) {
     
        return ((char[]) collection)[i];
      } else if (collection instanceof boolean[]) {
     
        return ((boolean[]) collection)[i];
      } else if (collection instanceof byte[]) {
     
        return ((byte[]) collection)[i];
      } else if (collection instanceof double[]) {
     
        return ((double[]) collection)[i];
      } else if (collection instanceof float[]) {
     
        return ((float[]) collection)[i];
      } else if (collection instanceof int[]) {
     
        return ((int[]) collection)[i];
      } else if (collection instanceof long[]) {
     
        return ((long[]) collection)[i];
      } else if (collection instanceof short[]) {
     
        return ((short[]) collection)[i];
      } else {
     
        throw new ReflectionException("The '" + prop.getName() + "' property of " + collection + " is not a List or Array.");
      }
    }
  }
  //根据属性名称取值
private Object getBeanProperty(PropertyTokenizer prop, Object object) {
     
    try {
     
      Invoker method = metaClass.getGetInvoker(prop.getName());
      try {
     
        return method.invoke(object, NO_ARGUMENTS);
      } catch (Throwable t) {
     
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (RuntimeException e) {
     
      throw e;
    } catch (Throwable t) {
     
      throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
    }
  }

小伙伴有没有被绕晕呢,反射的代码大家自己看下也不难。看完流程后再看代码其实很容易了

小结

小编就先到这儿,今天主要是说明了结果集处理的大致流程大家可以自行debug查看DefaultResultSetHandler类的方法,里面的细节还是很多的,再就是MetaObject的使用以及他是如何获取属性值的流程,设置值的流程留给大家了。希望继续关注小编,小编接下来将嵌套查询,懒加载延迟加载的细节。

你可能感兴趣的:(Mybatis核心源码,java,mybatis)