问题描述
SpringBoot 版本:2.5.2
项目原本的 MyBatisPlus 版本为:3.4.0,项目可以正常启动运行,但是在将 MyBatisPlus 的版本升级至 3.4.3.1之后,项目启动 MyBatisPlus 就开始报错了。
报错信息较多,在这里截取其中比较重要的几段:
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)
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
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
@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
对象,最终导致类型转换异常。
泛型检查?
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
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
方法中,有这么一段代码:
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。
我在查看 GitHub 相关记录后,确定了该问题是一个版本 BUG:MyBatisPlus升级3.4.3.1版本报错原因