手敲MyBatis(十二章)-ResultMap解析映射使用

1.前言

这一章节主要解决好几章之前留下的坑,需要根据XML配置的ReultMap进行解析映射成具体的PO供用户使用。

我们本章就来解决下在xml中配置了如下标红框的内容,怎么解析映射到具体的实体类中,如下就是将id为activityMap的resultMap怎么解析到type为Activity实体中,并将一个一个的redult的字段属性获取到以后映射到Activity的属性里,并在最终查询获取时用到了这个resultMap的结果进行属性值的反射就可以了,

手敲MyBatis(十二章)-ResultMap解析映射使用_第1张图片

下面这张图是xml与各属性之间对应的关系,我们需要根据这个关系的逻辑处理对应的映射。

首先需要有xml的resultMap标签,去定义标识id以及对应的PO实体type,然后对应的类resultMap去存储id以及type等信息,然后字段的就定义在result的column和property,主要一个是数据库字段属性,一个是实体里的属性,然后到时解析到resultMapping对应属性里,

解析完了以后执行sql语句时返回结果用的是resultMap,并把对应的resultMap类数据取出获得ResultMapping与数据库数据一一比对,匹配则进入反射并设置对应的数据库值即可。

代码还是蛮简单的,涉及的东西也不多,主要围绕着解析然后映射字段等处理来。

手敲MyBatis(十二章)-ResultMap解析映射使用_第2张图片

 2.UML类图

手敲MyBatis(十二章)-ResultMap解析映射使用_第3张图片

1.主要在XmlMapperBuilder里添加解析resultMap的方法,如resultMapElement方法,解析获取resultMap的id和type,并通过buildResultMappingFromContext方法解析resultMapping的属性和列,解析完毕构建存储到resultMapping,并最终存储到resultMap中,数据都处理好了,

2.resultMap数据都处理好了后,传递到ResultMapResolver里,最终由此调用MapperBuilderAssistant助手进行resultMap的构建。

3.Sql执行完处理结果时,添加新的映射结果集处理。

3.代码实现

3.1 XMLMapperBuilder

XMLMapperBuilder类修改如下:

在configurationElement方法里新增解析resultMap操作,新增resultMapElement方法用来解析resultMap,新增buildResultMappingFromContext方法来解析resultMapping,其实都不复杂就这点业务逻辑,打个断点可以看下

public class XMLMapperBuilder extends BaseBuilder {

// 省略其他

 private void configurationElement(Element element) {
        // 配置namespace   ,代码省略中
        

        // 2. 解析resultMap step-13 新增
        resultMapElements(element.elements("resultMap"));

        // select、insert、update、delete SQL解析
      
    }

 private void resultMapElements(List list) {
        for (Element element : list) {
            try {
                resultMapElement(element, Collections.emptyList());
            } catch (Exception ignore) {
            }
        }
    }


    /**
     * 解析resultMap
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     */
    private void resultMapElement(Element resultMapNode, List additionalResultMappings) {
        String id = resultMapNode.attributeValue("id");  // resultMap id
        String type = resultMapNode.attributeValue("type"); // resultMap type
        // 将resultMap的type转换Class
        Class typeClass = resolveClass(type);

        List resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);

        // resultMap所有的子result
        List resultChildren = resultMapNode.elements();
        for (Element resultChild : resultChildren) {
            List flags = new ArrayList();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            // ResultMapping添加到集合里
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
        // 创建结果映射解析器
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, resultMappings);
        resultMapResolver.resolve();
    }

    /**
     * 
     * 
     */
    private ResultMapping buildResultMappingFromContext(Element context, Class resultType, List flags) {
        String property = context.attributeValue("property"); // 需要被映射的属性,如:activityId
        String column = context.attributeValue("column");    // 原列,如activity_id
        return builderAssistant.buildResultMapping(resultType, property, column, flags);
    }
}

 3.2 MapperBuilderAssistant

MapperBuilderAssistant主要添加了构建resultMapping方法操作,以及获取javaType下属性的set方法的参数类型方法处理。

public class MapperBuilderAssistant extends BaseBuilder {
// step-13 新增方法
    public ResultMapping buildResultMapping(
            Class resultType,
            String property,
            String column,
            List flags) {
        // 获取resultType的里属性当前SET方法的参数类型
        Class javaTypeClass = resolveResultJavaType(resultType, property, null);// 列的结果类型
        TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);

        // 构建ResultMapping
        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
        builder.typeHandler(typeHandlerInstance);
        builder.flags(flags);
        return builder.build();
    }

     
     // new add
     // 根据resultType获取javaType
    private Class resolveResultJavaType(Class resultType, String property, Class javaType) {
        if (javaType == null && property != null) {
            try {
                MetaClass metaResultType = MetaClass.forClass(resultType);
                // 根据当前属性获取set参数类型
                javaType = metaResultType.getSetterType(property);

            } catch (Exception ignore) {
            }
        }
        if (javaType == null) {
            javaType = Object.class;
        }
        return javaType;
    }
}

3.3 BaseBuilder

BaseBuilder主要说新增两个从注册器获取类型操作,一个是resolveClass方法里从typeAliasRegistry获取,获取不到的化直接将当前类型反射成类(通过Resources.classForName(string)),

另一个是resolveTypeHandler方法里从typeHandlerRegistry里获取java类型,前提是typeHandlerType不为空,否则返回空,但是我们上边MapperBuilderAssistant类buildResultMapping()里传的是空,所以这里一点返回空,没关系后边会填这个坑

public class BaseBuilder {
// 省略其他...
 // 根据类型名称反射成Class 类型
    protected Class resolveClass(String alias) {
        if (alias == null) {
            return null;
        }
        try {
            return resolveAlias(alias);
        } catch (Exception e) {
            throw new RuntimeException("Error resolving class. Cause: " + e, e);
        }
    }
 
    // new add  获取TypeHandler
    protected TypeHandler resolveTypeHandler(Class javaType, Class> typeHandlerType) {
        if (typeHandlerType == null) {
            return null;
        }
        return typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
    }
}

3.4. ResultMapResolver

ResultMapResolver新增的类,在这里主要的方法就是调用助手类构建resultMap,特意封装了一个ResultMapResolver类。

ublic class ResultMapResolver {
    private final MapperBuilderAssistant assistant;
    private String id;
    private Class type;
    private List resultMappings;

    public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class type, List resultMappings) {
        this.assistant = assistant;
        this.id = id;
        this.type = type;
        this.resultMappings = resultMappings;
    }

    public ResultMap resolve() {
        // 通过助手将resultMapping放入resultMap里
        return assistant.addResultMap(this.id, this.type, this.resultMappings);
    }
}

3.5.ResultMapping

ResultMapping类的修改,新增了静态内部类帮忙构造ResultMapping所需要的属性,其中 resolveTypeHandler()方法就是解决上面说的typeHandler为空的问题,

public class ResultMapping {
    private Configuration configuration;
    private String property;
    private String column;
    private Class javaType;
    private TypeHandler typeHandler;
    private List flags;

    ResultMapping() {
    }

    public static class Builder {
        private ResultMapping resultMapping = new ResultMapping();

        public Builder(Configuration configuration, String property, String column,
                       Class javaType) {
            resultMapping.configuration = configuration;
            resultMapping.property = property;
            resultMapping.column = column;
            resultMapping.javaType = javaType;
            resultMapping.flags = new ArrayList<>();
        }

        public Builder typeHandler(TypeHandler typeHandler) {
            resultMapping.typeHandler = typeHandler;
            return this;
        }

        public Builder flags(List flags) {
            resultMapping.flags = flags;
            return this;
        }

        public ResultMapping build() {
            // 构建ResultMapping时TypeHandler为空,再次获取TypeHandler
            resolveTypeHandler();
            return resultMapping;
        }

        private void resolveTypeHandler() {
            if (resultMapping.typeHandler == null && resultMapping.javaType != null) {// javaType不为空,因为根据此来获取类型处理器
                Configuration configuration = resultMapping.configuration;
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, null);
            }
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public String getColumn() {
        return column;
    }

    public Class getJavaType() {
        return javaType;
    }

    public TypeHandler getTypeHandler() {
        return typeHandler;
    }

    public List getFlags() {
        return flags;
    }

}

3.6.resultMap

resultMap就只是更改了在build方法时将resultMapping.getColumn()转为大写存入mappedColumns中。

public class ResultMap {
    private String id;
    private Class type;
    private List resultMappings;
    private Set mappedColumns;

    public ResultMap() {
    }

    public static class Builder {
        private ResultMap resultMap = new ResultMap();

        public Builder(Configuration configuration, String id, Class type, List resultMappings) {
            resultMap.id = id;
            resultMap.type = type;
            resultMap.resultMappings = resultMappings;
        }


        public ResultMap build() {
            resultMap.mappedColumns = new HashSet<>();
            // new add
            for (ResultMapping resultMapping : resultMap.resultMappings) {
                final String column = resultMapping.getColumn();
                if (column != null) {
                    // 填充到已映射的列转化为大写,后期和查询出来的属性对比
                    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
                }
            }
            return resultMap;
        }
    }

    public String getId() {
        return id;
    }

    public Class getType() {
        return type;
    }

    public List getResultMappings() {
        return resultMappings;
    }

    public Set getMappedColumns() {
        return mappedColumns;
    }

    public List getPropertyResultMappings() {
        return resultMappings;
    }
}

3.7.Configuration

Configuration类中添加了resultMap的存储与获取

    // 结果映射,存在Map里-新增
    protected final Map resultMaps = new HashMap<>();

    public ResultMap getResultMap(String id) {
        return resultMaps.get(id);
    }

    public void addResultMap(ResultMap resultMap) {
        resultMaps.put(resultMap.getId(), resultMap);
    }

3.8.DefaultResultSetHandler

前面是解析,到DefaultResultSetHandler就是反射和使用了,在getRowValue()方法里添加新的方法applyPropertyMappings()迎来处理resultMap映射,然后跟上节其他的自动映射的操作一样获取java类型,根据类型从结果集获取结果值,然后通过反射工具往对象里设置值。

手敲MyBatis(十二章)-ResultMap解析映射使用_第4张图片

 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        // 根据返回值类型,实例化对象
        Object resultObject = createResultObject(rsw, resultMap, null);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
            final MetaObject metaObject = configuration.newMetaObject(resultObject);
            // 自动映射,把每列的值都赋给对应的字段上
            applyAutomaticMappings(rsw, resultMap, metaObject, null);
            // Map映射,根据映射类型赋值到字段
            applyPropertyMappings(rsw, resultMap, metaObject, null);
        }
        return resultObject;
    }


private boolean applyPropertyMappings(ResultSetWrapper rsw,
                                          ResultMap resultMap,
                                          MetaObject metaObject,
                                          String columnPrefix) throws SQLException {
        // 已映射的列名
        final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        // 获取ResultMapping数据
        final List propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
            final String column = propertyMapping.getColumn();
            if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
                // 获取值
                final TypeHandler typeHandler = propertyMapping.getTypeHandler();
                // 根据对应列类型获取值
                Object value = typeHandler.getResult(rsw.getResultSet(), column);
                // 设置值
                final String property = propertyMapping.getProperty();
                if (value != NO_VALUE && property != null && value != null) {
                    // 通过反射工具类设置属性值
                    metaObject.setValue(property, value);
                    foundValues = true;
                }
            }
        }
        return foundValues;
    }

4.测试准备

添加新的xml,名字为:Activity_Mapper.xml



    
        
        
        
        
        
        
    

    

 mybatis-config-datasource.xml,配置成Activity_Mapper.xml




    
        
            
            
                
                
                
                
            
        
    

    
        
        
    

 单元测试

  private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }


    // 单元
    @Test
    public void test_queryActivityById() {
        // 1. 获取映射器对象
        IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
        // 2. 测试验证
        Activity res = dao.queryActivityById(100001L);
        System.out.println(res.getActivityName());

        //logger.info("测试结果:{}", JSON.toJSONString(res));
    }

建表Sql语句

DROP TABLE IF EXISTS `activity`;
CREATE TABLE `activity`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `activity_id` bigint(20) NOT NULL COMMENT '活动ID',
  `activity_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '活动名称',
  `activity_desc` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '活动描述',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unique_activity_id`(`activity_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '活动配置' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of activity
-- ----------------------------
INSERT INTO `activity` VALUES (1, 100001, '活动名', '测试活动', '2021-08-08 20:14:50', '2021-08-08 20:14:50');
INSERT INTO `activity` VALUES (3, 100002, '活动名', '测试活动', '2021-10-05 15:49:21', '2021-10-05 15:49:21');

 单元测试完毕,拿到了想要的值

手敲MyBatis(十二章)-ResultMap解析映射使用_第5张图片

 

你可能感兴趣的:(MyBatis专栏,mybatis,java,开发语言)