每一秒钟的时间都值得铭记

0%

MyBatisPlus升级3.4.3.1版本报错:MybatisConfiguration$StrictMap$Ambiguity cannot be cast to ResultMap

问题描述

SpringBoot 版本:2.5.2
项目原本的 MyBatisPlus 版本为:3.4.0,项目可以正常启动运行,但是在将 MyBatisPlus 的版本升级至 3.4.3.1之后,项目启动 MyBatisPlus 就开始报错了。

报错信息较多,在这里截取其中比较重要的几段:

1
2
3
4
5
6
7
8
9
2021-06-29 23:25:42.015 [main] ERROR org.mybatis.spring.mapper.MapperFactoryBean - Error while adding the mapper 'interface com.zero.auth.mapper.UserMapper' to configuration.
org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'com/zero/auth/mapper/UserMapper.xml'. Cause: java.lang.ClassCastException: class com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap$Ambiguity cannot be cast to class org.apache.ibatis.mapping.ResultMap (com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap$Ambiguity and org.apache.ibatis.mapping.ResultMap are in unnamed module of loader 'app')
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:123)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.parse(XMLMapperBuilder.java:95)
at com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder.loadXmlResource(MybatisMapperAnnotationBuilder.java:172)
at com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder.parse(MybatisMapperAnnotationBuilder.java:93)
at com.baomidou.mybatisplus.core.MybatisMapperRegistry.addMapper(MybatisMapperRegistry.java:83)
at com.baomidou.mybatisplus.core.MybatisConfiguration.addMapper(MybatisConfiguration.java:119)
at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:80)
1
2
3
4
5
6
7
8
9
10
Caused by: java.lang.ClassCastException: class com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap$Ambiguity cannot be cast to class org.apache.ibatis.mapping.ResultMap (com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap$Ambiguity and org.apache.ibatis.mapping.ResultMap are in unnamed module of loader 'app')
at com.baomidou.mybatisplus.core.MybatisConfiguration.checkGloballyForDiscriminatedNestedResultMaps(MybatisConfiguration.java:318)
at com.baomidou.mybatisplus.core.MybatisConfiguration.addResultMap(MybatisConfiguration.java:222)
at org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap(MapperBuilderAssistant.java:209)
at org.apache.ibatis.builder.ResultMapResolver.resolve(ResultMapResolver.java:47)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:289)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:254)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElements(XMLMapperBuilder.java:246)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:119)
... 39 common frames omitted

报错信息分析

在报错信息中,报告了一个 java.lang.ClassCastException 异常,这是 Java 中的类型转换异常。
具体的信息为class com.baomidou.mybatisplus.core.MybatisConfiguration$StrictMap$Ambiguity cannot be cast to class org.apache.ibatis.mapping.ResultMap
一个 MybatisConfiguration$StrictMap$Ambiguity 类型的对象无法转换为 ResultMap 对象。

源码分析

经过一段时间的 Debug,最终还是定位到了原因。具体的原因可以看 MyBatisPlus 中的 com.baomidou.mybatisplus.core.MybatisConfiguration#checkGloballyForDiscriminatedNestedResultMaps 方法。

以下是 MyBatisPlus 中该方法的源代码:

MyBatisPlus 3.4.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
if (rm.hasNestedResultMaps()) {
for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
Object value = entry.getValue();
if (value instanceof ResultMap) {
ResultMap entryResultMap = (ResultMap) value;
if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
if (discriminatedResultMapNames.contains(rm.getId())) {
entryResultMap.forceNestedResultMaps();
}
}
}
}
}
}

MyBatisPlus 3.4.3.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
if (rm.hasNestedResultMaps()) {
for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
ResultMap entryResultMap = entry.getValue();
if (entryResultMap != null) {
if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
if (discriminatedResultMapNames.contains(rm.getId())) {
entryResultMap.forceNestedResultMaps();
}
}
}
}
}
}

通过上面的代码可以看到,在 3.4.0 版本中,Map 循环里面 getValue() 方法获取的是 Object 对象,而后有一层 instanceof 的安全类型检查。
而在 3.4.3.1 版本中,Map 循环直接获取的就是 ResultMap 对象,但是在该 Map 中存放的并不全是 ResultMap 对象,最终导致类型转换异常。

泛型检查?

1
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");

上面的代码中 resultMaps的就是checkGloballyForDiscriminatedNestedResultMaps 方法中循环的 resultMaps 对象,可以看到该 Map 对象声明了泛型,但是最终在存放元素的时候还是绕过了泛型检查。

resultMaps 使用的是 MyBatisPlus 自己实现了一个 Map 类型 StrictMap,可以去检查一下这个类型的 put 方法。

StrictMap

com.baomidou.mybatisplus.core.MybatisConfiguration.StrictMap

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
62
63
64
65
66
protected class StrictMap<V> extends HashMap<String, V> {

private static final long serialVersionUID = -4950446264854982944L;
private final String name;
private BiFunction<V, V, String> conflictMessageProducer;

public StrictMap(String name) {
super();
this.name = name;
}

public StrictMap<V> conflictMessageProducer(BiFunction<V, V, String> conflictMessageProducer) {
this.conflictMessageProducer = conflictMessageProducer;
return this;
}

@Override
@SuppressWarnings("unchecked")
public V put(String key, V value) {
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key
+ (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
}
if (useGeneratedShortKey) {
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
}
return super.put(key, value);
}

@Override
public V get(Object key) {
V value = super.get(key);
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
if (useGeneratedShortKey && value instanceof StrictMap.Ambiguity) {
throw new IllegalArgumentException(((StrictMap.Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}

protected class Ambiguity {
private final String subject;

public Ambiguity(String subject) {
this.subject = subject;
}

public String getSubject() {
return subject;
}
}

private String getShortName(String key) {
final String[] keyParts = key.split("\\.");
return keyParts[keyParts.length - 1];
}
}

StrictMap 重写的 put 方法中,有这么一段代码:

1
super.put(shortKey, (V) new Ambiguity(shortKey));

com.baomidou.mybatisplus.core.MybatisConfiguration.StrictMap.Ambiguity 类型的对象强转为 Value 泛型。
于是,一个原本就不是 ResultMap 类型的对象,就这么绕过泛型检查,直接存放进入 Map 集合中。

然后在 MyBatisPlus 3.4.3.1 版本中,不知道哪位同学将 instanceof 类型检查的代码去掉了,最终导致 MyBatisPlus 在升级至 3.4.3.1 版本中之后开始报错。

源码提交日志

我去 GiHub 上查看了 MyBatisPlus 3.4.0 版本至 3.4.3.1 版本之间的升级日志,没有一条更新日志提到这个问题,我个人觉得这是一个 BUG。

6月3号

一位 MyBatisPlus 的维护者提交了代码,对 com.baomidou.mybatisplus.core.MybatisConfiguration#checkGloballyForDiscriminatedNestedResultMaps 方法进行了优化,去掉了该方法中的 instanceof 的安全类型检查,具体如下:

在这里插入图片描述

从这位开发者的角度我觉得这段优化完全没有问题,毕竟这个 Map 容器使用了泛型,完全可以直接返回泛型类型对象,不需要多此一举使用 instanceof 进行安全类型检查。
但是这位开发者不知道的是,在这个 Map 容器里面,重写的 put 方法完全不讲武德,对非泛型类型的对象进行强转,放入 Map 容器中。

这位开发者完全是遭受了无妄之灾,这应该是当初设置这个 Map 容器的这位大神的问题,既然使用了泛型进行类型编译检查,就不应该在 put 的时候进行类型强制转行。或者说,既然需要存放不一致的类型,当初设计的时候就应该将泛型设置为 Object 类型。

6月15日

MyBatisPlus 发布了 3.4.3.1 版本,也就是包含这个 BUG 的版本。
在这里插入图片描述

6月16日

另外一位维护者似乎意识到了这个 BUG,提交了代码对该问题进行了修复,也就是将原始代码进行了还原。

在这里插入图片描述

最后

根据 GitHub 上的提交记录,很明显发现,这个问题是一个版本 BUG,虽然在最新的 GiHub 仓库中问题已经被修复了,但是 3.4.3.1 版本中依然存在该 BUG。

如果你在项目中使用 MyBatisPlus 3.4.3.1 版本,出现了我博客中描述的相关问题,那就只能通过修改版本来解决了。

要么降回原来的版本,或者等 MyBatisPlus 发布更新的版本,解决该 BUG。

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------这是我的底线^_^-------------