目录
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);
}
}
遍历resultMap根节点
private void resultMapElements(List<XNode> 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.<ResultMapping> emptyList());
}
首先进入第一道开胃菜:
1. resultMapElements, 它遍历了mapper下面所有的resultMap标签,然后每个都进行 resultMapElement 处理
2. resultMapElement 传入一个空的集合对象,调用重载
resultMapElement 为什么要传入一个空的集合对象呢?
那是因为resultMap
标签可以嵌套,但是根节点就是空的
重载的resultMapElement
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 | 其他->嵌套 |
其他流程-普通标签 id|result 标签
Xml 例子
<resultMap id="BlogMap" type="com.aya.mapper.Blog">
<id column="id" property="id"/>
</resultMap>
(id|result|association|collection)标签解析流程
List<ResultFlag> flags = new ArrayList<ResultFlag>();
// id 标签加入 flags, 其他的就不加入这个标签
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
这是最简单最常用的一种解析方式.
1. 添加标识 ID
2. 使用 buildResultMappingFromContext 构建成一个 ResultMapping 添加到集合 resultMappings
buildResultMappingFromContext
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> 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.<ResultMapping> 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
构建的
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) {
// 获得 <id column="id" property="id"/> 节点的 typeHandler 的值,本例未定义,结果为 null
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//根据typeHandlerClass获得对象,结果为 null
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
// 嵌套查询时解析为多个字段,例如: <association property="title" column="title={title},content={content}" ....
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);
// 组合列参数不可更改 <association column="title={title},content={content}" ....
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);
}
}
构造流程 constructor 标签
一个参数只要加入 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<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
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 标签 | collection 标签
association 用法
association 是一个对象的解析器,有两种用法
<select id="selectContent" resultType="string">
SELECT content FROM blog WHERE id = #{id}
</select>
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<!-- 直接使用对象,在result为对象赋值 -->
<association property="content" javaType="string" >
<result column="content" />
</association>
<!-- 使用 select 引用的查询结果,可以通过column传参 -->
<!-- 多参 key 是select引用需要用到变量,value是这边查询到的值 {id=id,content=content} -->
<association property="contentSelect" column="id" select="selectContent" javaType="string" />
</resultMap>
public class Blog {
private String content;
private String contentSelect;
//省略 getter , setter
}
collection 用法
collection 是一个集合的解析器,有两种用法
<select id="selectContentList" resultType="string">
SELECT content FROM blog WHERE id = #{id}
</select>
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
<!-- 直接使用集合, 那么bean的集合必须初始化 -->
<collection property="contentList" javaType="string" >
<result column="content" />
</collection>
<!-- 通过select 获取的集合一定不会为null. 所以bean的定义可以不用初始化 -->
<collection property="contentListSelect" column="id" select="selectContentList" javaType="list" />
</resultMap>
public class Blog {
public Blog() {
}
// 必须初始化
private List<String> contentList = new ArrayList<>();
// 可以不用初始化
private List<String> contentListSelect;
//省略 getter , setter
}
association | collection 流程分析
这里走的流程是其他标签解析的流程,但是比普通的标签有额外的两个方法要详细说明
在前面讲 buildResultMappingFromContext 的分析的时候,在进行 resultMap 获取是,有一个嵌套的处理。
就是专门用来处理具有嵌套效果的标签的, 而association | collection
刚好就是嵌套标签。
嵌套标签解析
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
嵌套多列处理
List<ResultMapping> composites = parseCompositeColumnName(column);
```
### 嵌套标签解析
private String processNestedResultMappings(XNode context, List 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 parseCompositeColumnName(String columnName) {
List composites = new ArrayList();
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);
}
也就是说,你不可以写成以下形式
## 辨别器流程 discriminator 标签
### 基本使用
<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>
实体类
```java
public class Blog {
private String id;
private String title;
//省略 getter , setter
}
<div class="se-preview-section-delimiter"></div>
查询结果
[Blog{id='1', title='标题'}, Blog{id='2', title='content2'}, Blog{id='3', title='null'}]
<div class="se-preview-section-delimiter"></div>
理论介绍
discriminator 是一个辨别器. 用switch...case
的方式决定让整个resultMap返回什么类型
也就是说,加载的时候只能是某个动态对象,执行查询操作后才知道实际的返回类型.
因为是switch...case
,所以不能加入条件判断
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
<div class="se-preview-section-delimiter"></div>
discriminator 的判断的地方可以发现, 如果有discriminator
,那么有且仅有一个.
接下来就分析Discriminator
对象的创建过程
源码分析
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> 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);
}
<div class="se-preview-section-delimiter"></div>
- 每个value都对应着一个
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<ResultFlag>(),
null,
null,
false);
Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
//构建辨别器的所有子节点
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
// 使用将简写改为全称
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
// 用建造者模式创建构建器
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
<div class="se-preview-section-delimiter"></div>
这里就通过 Discriminator 的建造者创建了对象.
Discriminator 是一个根据结果动态选择返回类型的一个标签,单纯的分析解析过程,意义不大
根标签解析
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
<div class="se-preview-section-delimiter"></div>
通过上一层的 builderAssistant.addResultMap ,实际上添加到哪里去了呢?
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> 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<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(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<ResultMapping> 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;
}
<div class="se-preview-section-delimiter"></div>
实际上那么多的解析从宏观上就只有两步
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>
<div class="se-preview-section-delimiter"></div>
public class Blog implements Serializable{
private Integer id;
private String title;
public Blog(Integer id, String title) {
this.id = id;
this.title = title;
}
}
以上面的代码为例,查询时在创建ResultMap时,抛出异常
Caused by: org.apache.ibatis.builder.BuilderException: Error in result map 'com.aya.mapper.BlogMapper.BlogMap'. Failed to find a constructor in 'com.aya.mapper.Blog' by arg names [id, title]. There might be more info in debug log.
at org.apache.ibatis.mapping.ResultMap$Builder.build(ResultMap.java:132)
at org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap(MapperBuilderAssistant.java:213)
at org.apache.ibatis.builder.ResultMapResolver.resolve(ResultMapResolver.java:47)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:285)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:252)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElements(XMLMapperBuilder.java:244)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:116)
... 29 more
打破脑袋也想不明白,明明已经提供了 id,title 类型的构造,参数类型也能匹配。为什么找不到呢?
跟进错误堆栈 ResultMap$Builder.build(ResultMap.java:132)
final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
if (actualArgNames == null) {
throw new BuilderException("Error in result map '" + resultMap.id
+ "'. Failed to find a constructor in '"
+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
+ ". There might be more info in debug log.");
当 argNamesOfMatchingConstructor
的结果为 null,抛出异常.
private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
// 获得构造器
Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
Class<?>[] paramTypes = constructor.getParameterTypes();
// 判断长度相等
if (constructorArgNames.size() == paramTypes.length) {
// 获得 constructor 参数名字列表为 : arg0, arg1
List<String> paramNames = getArgNames(constructor);
// [id,title].contains(arg0,arg1) 失败,结束循环
if (constructorArgNames.containsAll(paramNames)
&& argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
return paramNames;
}
}
}
return null;
}
这里的主要问题就在于 getArgNames
获得的居然是arg0,arg1, 并不是我们写好的参数id,title
private List<String> getArgNames(Constructor<?> constructor) {
List<String> paramNames = new ArrayList<String>();
List<String> actualParamNames = null;
final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 优先判断是否有注解
if (annotation instanceof Param) {
name = ((Param) annotation).value();
break;
}
}
if (name == null && resultMap.configuration.isUseActualParamName() && Jdk.parameterExists) {
if (actualParamNames == null) {
// 遍历构造器的参数列表,获取参数名称
actualParamNames = ParamNameUtil.getParamNames(constructor);
}
if (actualParamNames.size() > paramIndex) {
name = actualParamNames.get(paramIndex);
}
}
paramNames.add(name != null ? name : "arg" + paramIndex);
}
return paramNames;
}
}
现在就要解决通过反射无法获得参数名称的问题.
这里 getArgNames 提供给了我们一个额外的选择,在参数前面加入 @Param 注解.
既然是额外的选择,那么也就是说JDK也提供了一个参数,编译时,把参数名称也保存起来的方式
Param注解 是mybatis 提供的一个扩展,而方案2,方案3都是开启JDK8的特性编译器保留方法参数名字
方案1 Param注解
推荐使用
public class Blog implements Serializable{
private Integer id;
private String title;
public Blog(@Param("id")Integer id,@Param("title") String title) {
this.id = id;
this.title = title;
}
}
使用这个虽然写起来麻烦了一些,但是利于维护,不用担心别人看不懂。或者由于不同的环境导致不一样的效果
方案2 maven 设置
添加 maven 插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
引用: https://blog.csdn.net/aitangyong/article/details/54376991
方案3 idea设置
打开File => Settings
依次选择 Buld,Execution,Deployment => Compiler => Java Compiler
设置 Additional command line parameters: -parameters
方案4 黑科技
切勿使用
黑科技: 既然不不开启特性,又想对应,那我就满足你的原理
<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>
总结
- resultMap 有6个子标签
- select属性关联集合不会为null