1.前言
这一章节主要解决好几章之前留下的坑,需要根据XML配置的ReultMap进行解析映射成具体的PO供用户使用。
我们本章就来解决下在xml中配置了如下标红框的内容,怎么解析映射到具体的实体类中,如下就是将id为activityMap的resultMap怎么解析到type为Activity实体中,并将一个一个的redult的字段属性获取到以后映射到Activity的属性里,并在最终查询获取时用到了这个resultMap的结果进行属性值的反射就可以了,
下面这张图是xml与各属性之间对应的关系,我们需要根据这个关系的逻辑处理对应的映射。
首先需要有xml的resultMap标签,去定义标识id以及对应的PO实体type,然后对应的类resultMap去存储id以及type等信息,然后字段的就定义在result的column和property,主要一个是数据库字段属性,一个是实体里的属性,然后到时解析到resultMapping对应属性里,
解析完了以后执行sql语句时返回结果用的是resultMap,并把对应的resultMap类数据取出获得ResultMapping与数据库数据一一比对,匹配则进入反射并设置对应的数据库值即可。
代码还是蛮简单的,涉及的东西也不多,主要围绕着解析然后映射字段等处理来。
2.UML类图
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<Element> list) {
for (Element element : list) {
try {
resultMapElement(element, Collections.emptyList());
} catch (Exception ignore) {
}
}
}
/**
* 解析resultMap
* <resultMap id="activityMap" type="po.Activity">
* <id column="id" property="id"/>
* <result column="activity_id" property="activityId"/>
* <result column="activity_name" property="activityName"/>
* <result column="activity_desc" property="activityDesc"/>
* <result column="create_time" property="createTime"/>
* <result column="update_time" property="updateTime"/>
* </resultMap>
*/
private void resultMapElement(Element resultMapNode, List<ResultMapping> additionalResultMappings) {
String id = resultMapNode.attributeValue("id"); // resultMap id
String type = resultMapNode.attributeValue("type"); // resultMap type
// 将resultMap的type转换Class
Class<?> typeClass = resolveClass(type);
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// resultMap所有的子result
List<Element> resultChildren = resultMapNode.elements();
for (Element resultChild : resultChildren) {
List<ResultFlag> 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();
}
/**
* <id column="id" property="id"/>
* <result column="activity_id" property="activityId"/>
*/
private ResultMapping buildResultMappingFromContext(Element context, Class<?> resultType, List<ResultFlag> 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<ResultFlag> 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<? extends TypeHandler<?>> 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<ResultMapping> resultMappings;
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, List<ResultMapping> 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<ResultFlag> 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<ResultFlag> 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<ResultFlag> getFlags() {
return flags;
}
}
3.6.resultMap
resultMap就只是更改了在build方法时将resultMapping.getColumn()转为大写存入mappedColumns中。
public class ResultMap {
private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private Set<String> mappedColumns;
public ResultMap() {
}
public static class Builder {
private ResultMap resultMap = new ResultMap();
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> 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<ResultMapping> getResultMappings() {
return resultMappings;
}
public Set<String> getMappedColumns() {
return mappedColumns;
}
public List<ResultMapping> getPropertyResultMappings() {
return resultMappings;
}
}
3.7.Configuration
Configuration类中添加了resultMap的存储与获取
// 结果映射,存在Map里-新增
protected final Map<String, ResultMap> 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类型,根据类型从结果集获取结果值,然后通过反射工具往对象里设置值。
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<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取ResultMapping数据
final List<ResultMapping> 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
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="df.middleware.mybatis.dao.IActivityDao">
<resultMap id="activityMap" type="df.middleware.mybatis.po.Activity">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="activity_name" property="activityName"/>
<result column="activity_desc" property="activityDesc"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryActivityById" parameterType="java.lang.Long" resultMap="activityMap">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
WHERE activity_id = #{activityId}
</select>
</mapper>
mybatis-config-datasource.xml,配置成Activity_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- XML 配置 -->
<mapper resource="mapper/Activity_Mapper.xml"/>
</mappers>
</configuration>
单元测试
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');
单元测试完毕,拿到了想要的值