MyabtisPlus(1):ORM

423 阅读5分钟

前言

  • 在此之前需要掌握以下要点:
  1. 反射常见方法
  2. 常用设计模式
  3. 多线程相关知识
  4. JDBC,毕竟Mybatis底层是对JDBC的封装,若能知道JDBC原理最好
  5. 会配置并且使用mybatisPlus
  6. 会整合mybatisPlus与springBoot,本系列使用的是单数据源,即指定spring.datasource属性
  • 使用技术
  1. mybatis-plus 3.2.0,内含mybatis 3.5.1
  2. spring-boot 2.1.1.RELEASE
  3. mysql数据库
  4. 数据库连接池使用druid,版本为1.1.9

ORM映射结果集的实现

DemoController->test():
    Tree tree = service.getById(2);
PreparedStatementHandler->query():
    // ps内部封装了PreparedStatement,最终执行的是ps的execute()
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行完execute()之后,结果集原始对象封装到了ps的stmt的statment的delegate的results的rowData中
    ps.execute();
    // 结果集处理器处理ps
    return resultSetHandler.handleResultSets(ps);
DefaultResultSetHandler->handleResultSets():
    // 最终结果集
    final List<Object> multipleResults = new ArrayList<>();
    // rsw对象封装了返回的字段名、字段类型(Java与MySQL类型)、rs/ps对象(位于resultSet变量的rs与wrapper中)
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取Tree的相关信息
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    ......
    // 最终multipleResults存储了一个List类型的对象,这个对象存储了我们想要tree对象
    // handleResultSet方法最后还很好心地帮我们执行了rs.close()
    handleResultSet(rsw, resultMap, multipleResults, null); // --1
    ......
    // multipleResults只有一个list的话,那就只返回一个list
    // 至此,解析完成,情景处返回的是ArrayList,其值为list[0]=new Tree(2,5)
    // 返回过程中还会很好心地执行ps.close()
    return collapseSingleResultList(multipleResults);
DefaultResultSetHandler->handleResultSet(): // --1
    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
DefaultResultSetHandler->handleRowValues():
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
DefaultResultSetHandler->handleRowValuesForSimpleResultMap():
    // resultSet.next()最终调用的是JDBC的rs的next()方法
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()){
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 这一步是将一条结果实例化成我们想要的对象,然后添加到resultHandler变量中(resultHandler是一个继承了List接口的ArrayList类型的对象,代码见DefaultObjectFactory->resolveInterface())
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
    ......
DefaultResultSetHandler->getRowValue():
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);  // --2
    // 如果该值不为空,并且返回实体类不对应JDBC类型
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        ......
        // 如果配置了应用自动映射的话,这边最终会调用反射方法method.invoke(target, args)给对象赋值(类似于tree.setId(2))
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
    }
DefaultResultSetHandler->applyAutomaticMappings():
    // 是否自动映射,首先获取返回的字段名称,遍历,看字段是否有对应实体类的set方法
    // 然后看该字段类型是否对应JDBC类型,有的话,说明是自动映射,然后进行赋值的操作
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); // --3
    if (!autoMapping.isEmpty()) {
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
        // 获得结果
        // 根据typeHandler决定用哪一种handler,比如是Long类型,那么最终调用的就是rs.getLong("columnName")
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
            foundValues = true;
        }
        // 如果值为空,那么不会给对象赋值
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
            // 最终会调用method.invoke(target, args)给对象赋值
            metaObject.setValue(mapping.property, value);
        }
    }
    return foundValues;
DefaultResultSetHandler->createAutomaticMappings(): // --3
    // 先从缓存获取,如果缓存没有的话,利用columnDefinition获取返回的字段名称等属性
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
        autoMapping = new ArrayList<>();
        // select几个字段,unmappedColumnNames的长度就为多少
        final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
DefaultResultSetHandler->createResultObject():  // --2
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
DefaultResultSetHandler->createResultObject():    
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
        // 如果只返回一个字段,并且该字段对应JDBC的类型,直接返回该值 
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix); // 1
    } else if (!constructorMappings.isEmpty()) {
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
        // 如果类是接口或者有默认无参构造器,最终调用无参构造器方法
        return objectFactory.create(resultType);  // 2
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 会抽取类的构造器,获取返回的字段名称columnName,根据构造器参数类型调用相应的get方法(如第一个参数是String类型,第一个返回字段为column1,则底层调用rs.getString("column1"))
        // 最后使用反射方法Tree.class.getConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()])).newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]))实例化类
        return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    }

总结与反思:

  1. 映射关系

    1. 如果数据库只有a字段,却选择b字段,是在接收字节流解析的时候发现的(同JDBC)
    2. 如果数据库只有a字段,却返回a+1字段,在自动映射的时候会发现没有set(a+1)这个方法,因此不会自动映射
    3. 如果数据库是varchar,实体类却是int,获取值的时候会调用rs.getInt(columnName)方法
    4. 如果实体类属性有a,b,c,select的是a,b,那么在做字段映射的时候只会set(a),set(b)
    5. 如果mysql返回的字段值为null,那么不会做set操作
  2. DefaultResultSetHandler->createResultObject()方法中,如果使用了有参构造(String a,Integer b),数据库返回的是c、d字段,那么最终会调用rs.getString(c),rs.getInteger(d),然后将这两个值赋给a和b

    • 如果rsw只返回一个字段,并且该字段对应JDBC的类型,走--2中的1
    • 如果有无参构造器,使用无参构造器
    • 如果没有无参构造器,只有一种构造器,使用该构造器
    • 如果有多种构造器,使用全构造器
    • 如果多种构造器都没有全构造器的话,会抛异常

    不管这边怎么实例化的,只要不是null,最终都会判断是否是对应JDBC类型以及是否自动映射

  3. MetaObject讲解:

假如对象a下有个对象b,对象b下有个int类型的字段id

MetaObject类:
    // 原对象
    private final Object originalObject;
    // 对象包装  如果是BeanWrapper类型,object为原类型,metaClass封装了原类型的get/set/construct等信息
    private final ObjectWrapper objectWrapper;
    private final ObjectFactory objectFactory;
    private final ObjectWrapperFactory objectWrapperFactory;
    private final ReflectorFactory reflectorFactory;
PropertyTokenizer类:
    // name与indexName相同,children为字符串第一个.之后的字符串
    private String name;
    private final String indexedName;
    private final String children;
----------------------------------------------------------------------------------------
MetaObject->getValue(String name):
    // 解析字符串
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // 如果字符串.后面有内容
    if (prop.hasNext()) {
        // b.id,那么metaValue就是对象b的相关信息
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        } else {
            // 继续调用getValue方法,这里的metaValue是b,以此类推,直到最后一个.前面
            return metaValue.getValue(prop.getChildren());
        }
    } else {
        // 利用对象工厂来获取值
        // 如果是map类型,返回map.get(name)
        // 如果是bean类型,先获取getName反射方法,再利用反射方法返回值,此处最终调用的是b.getId()
        return objectWrapper.get(prop);
    }
MetaObject->metaObjectForProperty():
    // 这一步是获取值
    Object value = getValue(name);
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
  1. getById/selectList与xml/mapper中的ORM映射实现方式一模一样

  2. 参数类型对应JDBC类型(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))解释:

  • 即参数对象类型有相应的类型处理器
  • 如int型对应IntegerTypeHandler,String对应StringTypeHandler,bean类一般来说都是没有的
  1. 连接池使用责任链模式的用途和意义
if (this.pos < filterSize) {
    return nextFilter().xxxxxxA();
}
return xxxxxxB();

虽然最终都会走到xxxxxxB()方法,但是在执行xxxxxxB()方法前可以做一些检验以及监控工作