目录
mybatis 有9
大标签,分别是:
- cache-ref
- cache
- parameterMap
- resultMap
- sql
- select
- insert
- update
- delete
其中 parameterMap
已被官网标记为废弃
cache-ref
和 cache
在这个电商横行,分布式肆虐的年代,也不使用 mybatis 缓存了
那么常用的只有其中6种
- resultMap
- sql
- select
- insert
- update
- delete
本节我们针对resultMap
进行详细分析
分析结束之后我们可以解答内心的一些疑问:
1. resultMap 有哪些子标签,都用来干什么
2. collection 标签为什么有时候
要自己初始化(new ArrayList<>())
先从 XMLMapperBuilder 的 configurationElement 说起, 这里存放着9大标签的解析
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// cache-ref 标签解析
cacheRefElement(context.evalNode("cache-ref"));
// cache 标签解析
cacheElement(context.evalNode("cache"));
// parameterMap 标签解析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// resultMap 标签解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
// sql 标签解析
sqlElement(context.evalNodes("/mapper/sql"));
// select|insert|update|delete 标签解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
private void resultMapElements(List list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections. emptyList());
}
首先进入第一道开胃菜:
1. resultMapElements, 它遍历了mapper下面所有的resultMap标签,然后每个都进行 resultMapElement 处理
2. resultMapElement 传入一个空的集合对象,调用重载
resultMapElement 为什么要传入一个空的集合对象呢?
那是因为resultMap
标签可以嵌套,但是根节点就是空的
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
// 保存当前上下文,用于异常信息回溯
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获得 id 标签
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 依次获取默认值 type=>ofType=>resultType>javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 获得 继承 resultMap 的 id
String extend = resultMapNode.getStringAttribute("extends");
// 获得 是否自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 遍历子节点,依次解析为 ResultMapping 对象,并添加到集合 resultMappings
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 解析验证,最终添加到 configuration
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
看似很多,实际上就是两步
1. 遍历解析子标签
为 ResultMapping 对象
2. 解析根标签
为一个ResultMap并添加到configuration
这里又分三种情况
1. constructor 子标签解析
2. discriminator 子标签解析
3. 其他标签解析
那resultMap 的子标签又有哪些,其他标签都包含哪些呢?
名称 | 类型 |
constructor | constructor |
discriminator | discriminator |
id | 其他 |
result | 其他 |
association | 其他->嵌套 |
collection | 其他->嵌套 |
Xml 例子
<resultMap id="BlogMap" type="com.aya.mapper.Blog">
<id column="id" property="id"/>
resultMap>
(id|result|association|collection)标签解析流程
List flags = new ArrayList();
// id 标签加入 flags, 其他的就不加入这个标签
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
这是最简单最常用的一种解析方式.
1. 添加标识 ID
2. 使用 buildResultMappingFromContext 构建成一个 ResultMapping 添加到集合 resultMappings
private ResultMapping buildResultMappingFromContext(XNode context, Class> resultType, List flags) throws Exception {
String property;
// constructor 标签用name当做属性
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
// 普通标签 标签用property当做属性
property = context.getStringAttribute("property");
}
// 废话开始
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
// processNestedResultMappings 嵌套解析在 association|collection 讲解
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections. emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 废话结束
//构建 ResultMapping
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
对于简单的id|result标签解析而言,这里面大都是将xml解析成具体的数据
这里要明白两点
1. 每一个 id|result|association|collection
标签,都对应着一个 ResultMapping
对象
2. ResultMapping 是通过builderAssistant.buildResultMapping
构建的
public ResultMapping buildResultMapping(
Class> resultType,
String property,
String column,
Class> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class extends TypeHandler>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
// 获得 节点的 typeHandler 的值,本例未定义,结果为 null
Class> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//根据typeHandlerClass获得对象,结果为 null
TypeHandler> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
// 嵌套查询时解析为多个字段,例如:
List<ResultMapping> composites = parseCompositeColumnName(column);
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<ResultFlag>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
这里终于找到终点了
ResultMapping.Builder
是一个很标准的建造者模式
。 符合建造者模式
的核心思想。
1. 必要的参数使用构造方法传入
2. 可选的设置使用 withXXX 设置
3. build 之后对象不可变
在build 的时候,做了一个默认的类型解析处理
public ResultMapping build() {
// lock down collections
// 内置标识 不可更改
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
// 组合列参数不可更改
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
// 设置默认的类型处理器
resolveTypeHandler();
// 验证嵌套的信息有效
validate();
// 返回构建成功的 resultMapping
// resultMapping 是成员变量
return resultMapping;
}
private void resolveTypeHandler() {
// 用户没有在标签定义 typeHandler时,使用mybatis默认的typeHandler
if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
Configuration configuration = resultMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
}
}
一个参数只要加入 javaType 就可以匹配到具体的构造函数了,不加javaType
时,默认匹配 ctor(Object) 的构造
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<constructor>
<idArg column="pid" javaType="integer"/>
constructor>
resultMap>
这里走流程 if ("constructor".equals(resultChild.getName()))
的代码块,分析 processConstructorElement
的内部
private void processConstructorElement(XNode resultChild, Class> resultType, List resultMappings) throws Exception {
List argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List flags = new ArrayList();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
将 constructor 的每个子标签构建成 ResultMapping 对象,添加到 resultMappings
也就是两个步骤
1. 添加标识 ResultFlag.CONSTRUCTOR ,构建 ResultMapping
2. 添加子标签 ,每个 ResultMapping 都有标识ResultFlag.CONSTRUCTOR
association 是一个对象的解析器,有两种用法
<select id="selectContent" resultType="string">
SELECT content FROM blog WHERE id = #{id}
select>
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<association property="content" javaType="string" >
<result column="content" />
association>
<association property="contentSelect" column="id" select="selectContent" javaType="string" />
resultMap>
public class Blog {
private String content;
private String contentSelect;
//省略 getter , setter
}
collection 是一个集合的解析器,有两种用法
<select id="selectContentList" resultType="string">
SELECT content FROM blog WHERE id = #{id}
select>
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<collection property="contentList" javaType="string" >
<result column="content" />
collection>
<collection property="contentListSelect" column="id" select="selectContentList" javaType="list" />
resultMap>
public class Blog {
public Blog() {
}
// 必须初始化
private List contentList = new ArrayList<>();
// 可以不用初始化
private List contentListSelect;
//省略 getter , setter
}
这里走的流程是其他标签解析的流程,但是比普通的标签有额外的两个方法要详细说明
在前面讲 buildResultMappingFromContext 的分析的时候,在进行 resultMap 获取是,有一个嵌套的处理。
就是专门用来处理具有嵌套效果的标签的, 而association | collection
刚好就是嵌套标签。
嵌套标签解析
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections. emptyList()));
嵌套多列处理
List composites = parseCompositeColumnName(column);
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}
这里做的就是当 association|collection|case
标签没有属性select
的时候,递归解析子标签
private List<ResultMapping> parseCompositeColumnName(String columnName) {
List<ResultMapping> composites = new ArrayList<ResultMapping>();
if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
//例如 column="{columnKeyA=columnValueA,columnKeyB=columnValueB}" 分两次遍历,添加到composites
while (parser.hasMoreTokens()) {
String property = parser.nextToken();
String column = parser.nextToken();
ResultMapping complexResultMapping = new ResultMapping.Builder(
configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
composites.add(complexResultMapping);
}
}
return composites;
}
这里就是来解析标签中 column="{columnKeyA=columnValueA,columnKeyB=columnValueB}"
部分的代码
装配成ResultMapping
集合,在最后build里面的validate进行验证
validate 验证嵌套
// Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
}
也就是说,你不可以写成以下形式
<collection property="contentList" javaType="string" >
<result />
collection>
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<result column="pid" property="id"/>
<discriminator javaType="string" column="pid" >
<case value="1" resultType="com.aya.mapper.Blog">
<result column="ptitle" property="title"/>
case>
<case value="2" resultType="com.aya.mapper.Blog">
<result column="pcontent" property="title"/>
case>
discriminator>
resultMap>
<select id="selectAll" resultMap="BlogMap" >
select id as pid,title as ptitle,content as pcontent from blog
select>
实体类
public class Blog {
private String id;
private String title;
//省略 getter , setter
}
查询结果
[Blog{id='1', title='标题'}, Blog{id='2', title='content2'}, Blog{id='3', title='null'}]
discriminator 是一个辨别器. 用switch...case
的方式决定让整个resultMap返回什么类型
也就是说,加载的时候只能是某个动态对象,执行查询操作后才知道实际的返回类型.
因为是switch...case
,所以不能加入条件判断
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
discriminator 的判断的地方可以发现, 如果有discriminator
,那么有且仅有一个.
接下来就分析Discriminator
对象的创建过程
private Discriminator processDiscriminatorElement(XNode context, Class> resultType, List resultMappings) throws Exception {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<String, String>();
//遍历case节点
for (XNode caseChild : context.getChildren()) {
// case 的value
String value = caseChild.getStringAttribute("value");
//processNestedResultMappings 解析嵌套的ResultMapping,并返回创建的名称
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
// 构建 Discriminator
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
ResultMapping
名称,在根据查询结果进行转换public Discriminator buildDiscriminator(
Class> resultType,
String column,
Class> javaType,
JdbcType jdbcType,
Class extends TypeHandler>> typeHandler,
Map<String, String> discriminatorMap) {
// 将辨别器节点构建成一个 ResultMapping
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList(),
null,
null,
false);
Map namespaceDiscriminatorMap = new HashMap();
//构建辨别器的所有子节点
for (Map.Entry e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
// 使用将简写改为全称
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
// 用建造者模式创建构建器
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
这里就通过 Discriminator 的建造者创建了对象.
Discriminator 是一个根据结果动态选择返回类型的一个标签,单纯的分析解析过程,意义不大
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
通过上一层的 builderAssistant.addResultMap ,实际上添加到哪里去了呢?
public ResultMap addResultMap(
String id,
Class> type,
String extend,
Discriminator discriminator,
List resultMappings,
Boolean autoMapping) {
// 简称改为全称
id = applyCurrentNamespace(id, false);
//继承
extend = applyCurrentNamespace(extend, true);
//继承解析
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List extendedResultMappings = new ArrayList(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
//ResultMap.Builder 构建 ResultMap. 根标签 resultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 将根标签解析的对象resultMap添加到configuration
configuration.addResultMap(resultMap);
return resultMap;
}
实际上那么多的解析从宏观上就只有两步
1. 将resultMap的所有子标签解析成ResultMapping对象
2. 将resultMap标签构建成ResultMap对象添加到configuration
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<constructor>
<idArg column="pid" name="id" javaType="integer"/>
<arg column="ptitle" name="title" javaType="string"/>
constructor>
resultMap>
public class Blog implements Serializable{
private Integer id;
private String title;
public Blog(Integer id, String title) {
this.id = id;
this.title = title;
}
}
以上面的代码为例,查询时在创建ResultMap时,抛出异常
打开File => Settings
依次选择 Buld,Execution,Deployment => Compiler => Java Compiler
设置 Additional command line parameters: -parameters
切勿使用
黑科技: 既然不不开启特性,又想对应,那我就满足你的原理
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<constructor>
<idArg column="pid" name="arg0" javaType="integer"/>
<arg column="ptitle" name="arg1" javaType="string"/>
constructor>
resultMap>