mybatis从1.3.2 升级 2.0.7到后部分系统解析 alias出错了,具体错误如下:
问题触发位置:
下面从TypeAliasRegistry.registerAlias 开始逐步分析两个版本差异和抛出问题原因。
两个版本的 TypeAliasRegistry.registerAlias 逻辑一致。具体代码如下。
# TypeAliasRegistry.java
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
从代码中可以看出,mybatis别名使用 class 的 simpleName。如果多个包中出现实体名称,那么就会发送如上错误。
但是开发反馈mybatis升级前不存在该问题,需要继续向上追踪代码。下面是根据升级前后两个版本别名设置分析。
mybatis-spring 1.3.2 typeAliasesPackage 解析逻辑如下
# SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
...
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
...
}
# TypeAliasRegistry
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
跟踪代码发现 typeAliasesPackage 不支持路径正则表达式;*实际配置 type-aliases-package: cn.pinming.suppervision..entity 无效。***
mybatis-spring 2.0.7 typeAliasesPackage 源码分析
#SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
}
private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
Set<Class<?>> classes = new HashSet<>();
String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packagePattern : packagePatternArray) {
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
classes.add(clazz);
}
} catch (Throwable e) {
LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
}
}
}
return classes;
}
该版本 typeAliasesPackage 已经支撑路径正则表达式。如 /a/b//c。原来解析不了的配置现在能解析了。**
由于旧版本 mybatis 的 typeAliasesPackage配置不支撑正则表达式,原来的配置是无效的,所以没有报错。而新版本支撑正则表达式,能将以前无效的配置解析出来,但存在类名简称存在重复。所以导致出现了上面描述的问题。
方案一、项目中如果存在同名实体,修正同名实体。
方案二、移除typeAliasesPackage。不在mybatis中使用别名。比如输入不存在的报名:cn.pinming.suppervision.aaaaa
mybatis:
base-packages: cn.pinming.suppervision.**.dao,cn.pinming.suppervision.push.mapping,cn.pinming.suppervision.web.test.sys
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: cn.pinming.suppervision.aaaaa
问题原因:新版本 mapper-spring-boot-starter 提供了 mapper-spring-boot-autoconfigure 实现,和自己定义的 有冲突。排除依赖即可。
解决方案
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>