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 就开始报错了。

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

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版本报错原因

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值