需求描述
修改已有的结果集,可以对查询的字段与 Java 属性直接进行任意的映射。
实际项目中,为了防止修改表结构而造成锁表,因此采用拓展表放置新的字段,为了不修改原有业务逻辑,故需要使用切面来处理结果集新增的部分字段,将其映射到 Map 中。
解决方案
ps: 通过修改 XML 可以完成所有复杂查询,但要考虑到方案适配的通用性,XML ヾ(•ω•`)o。
Mybatis 内置的 ResultHandler 拦截器可以对其 ResultMap 对象进行拦截。
关键的数据结构
ResultMap 是 Mybatis 的结果集映射对象,与 XML 中 相对应,其中通过 ResultMapping 定义了每一列字段与 Java 属性之间的映射关系,具体数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class ResultMap { private Configuration configuration;
private String id; private Class<?> type; private List<ResultMapping> resultMappings; private List<ResultMapping> idResultMappings; private List<ResultMapping> constructorResultMappings; private List<ResultMapping> propertyResultMappings; private Set<String> mappedColumns; private Set<String> mappedProperties; private Discriminator discriminator; private boolean hasNestedResultMaps; private boolean hasNestedQueries; private Boolean autoMapping; }
private Configuration configuration; private String property; private String column; private Class<?> javaType; private JdbcType jdbcType; private TypeHandler<?> typeHandler; private String nestedResultMapId; private String nestedQueryId; private Set<String> notNullColumns; private String columnPrefix; private List<ResultFlag> flags; private List<ResultMapping> composites; private String resultSet; private String foreignColumn; private boolean lazy; }
|
获取 ResultMap 对象
ResultMap 可以通过 MappedStatement 获取,且获取的是一个不可修改的 List 集合。因此 ResultMap 的修改要点:
- 遍历所有结果集映射;
- 反射重置结果集列表;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ResultHanlderInterceptor implements Interceptor {
@Override public Object intercept(Invocation invocation) { DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) invocation.getTarget(); MetaObject metaStatementHandler = SystemMetaObject.forObject(defaultResultSetHandler); MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement"); for (ResultMap resultMap: mappedStatement.getResultMap) { } } }
|
重建 ResultMap
ResultMap 的获取并不复杂,事实上很轻易的就可以拿到,通过调试或者查看源码,可知,通过 getter 方法可以得到列映射关系,ResultMapping,同样这也是不可修改的 List 集合,但不同的是,直接通过反射修改是不会生效的,所有的(列或结果集)映射关系必须要先向 mybatis 的 configuration 注册才行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public static ResultMap rebuild(MappedStatement ms, ResultMap resultMap) { MapperBuilderAssistant builderAssistant = getMapperBuilderAssistant(ms); List<ResultMapping> resultMappings = Lists.newArrayList(resultMap.getResultMappings().iterator()); resultMappings.add(getResultMapping()); ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, "id,唯一标识,不可重复"), resultMap.getType(), null, null, Collections.unmodifiableList(resultMappings), 是否自动映射); return resultMapResolver.resolve(); }
public ResultMapping getResultMapping() { return new ResultMapping.Builder(builderAssistant.getConfiguration(), getProperty("属性名")) .column("列名")) .typeHandler(类型处理器.class) .jdbcType(JdbcType.value)) .javaType(Java 类型.class) .nestedResultMapId("嵌套的 ResultMap id, 需要先注册") .build()) }
private static MapperBuilderAssistant getMapperBuilderAssistant(MappedStatement ms) { Configuration configuration = ms.getConfiguration(); String resource = ms.getResource(); String nameSpace = ms.getId().substring(0, ms.getId().lastIndexOf(".")); MapperBuilderAssistant builderAssistant = new MapperBuilderAssistant(configuration, resource); builderAssistant.setCurrentNamespace(nameSpace); return builderAssistant; }
|
注意事项
- 对应 (XML 文件)没有 ResultMap(或使用 ResultType)的结果集,需要设置自动映射(autoMapping = true),否则可能出现属性丢失的问题;
- 仅仅(通过反射)修改 ResultMap 的 resultMappings 属性不会生效,因为 ResultMapping 用于记录映射关系,却不是 Mybatis Configuration 真实的读取的信息,需要通过 ResultMapResolver 解析器读取信息,并注册到配置中心;
- 对于使用线程变量做拦截器标记时(如 PageHelper),在最终返回时拦截器结束时需要对线程变量进行 clear,谨防 OOM;
- 也可以在拦截器中直接读取 ResultSet,手动进行映射,或使用 Json 等工具,最终返回格式化后的对象,但需要考虑到列名与属性名不一样的场景,且对 Mybatis 映射产生破坏(不推荐);
总结
通过修改 ResultMap 可以任意的进行结果集映射,就像在 XML 中定义了 标签一样,但不同的是,其可以通过注解或参数等方式,对某个查询进行标记,可以在运行时动态的修改返回查询结果,且 ResultMap 会被 Mybaits 自动缓存。