甚至不愿听朋友说真话的人,是真正不可救药的人——西塞罗

之前使用byte-buddy实现mybatis-plus动态mapper

但是使用过程中发现一个问题,相关的issue链接:

https://gitee.com/VampireAchao/stream-query/issues/I6EJ27

在项目中已经定义了Mapper,如果在动态mapper已经注入的情况下,没法再通过Database.execute方法拿到,而是拿到的动态Mapper

进而导致大部分只要是基于execute方法的函数都是这样

于是为了解决这个问题,在DefaultSqlInjector处进行了处理

io.github.vampireachao.stream.plugin.mybatisplus.engine.configuration.StreamPluginAutoConfiguration下,当前的代码如下:

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
67
68
69
70
package io.github.vampireachao.stream.plugin.mybatisplus.engine.configuration;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.mapper.Mapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import io.github.vampireachao.stream.core.lambda.LambdaHelper;
import io.github.vampireachao.stream.core.reflect.ReflectHelper;
import io.github.vampireachao.stream.plugin.mybatisplus.Database;
import io.github.vampireachao.stream.plugin.mybatisplus.engine.enumration.SqlMethodEnum;
import io.github.vampireachao.stream.plugin.mybatisplus.engine.methods.SaveOneSql;
import io.github.vampireachao.stream.plugin.mybatisplus.engine.methods.UpdateOneSql;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;

import java.util.List;

/**
* MPSql注入
*
* @author VampireAchao Cizai_
*/
public class StreamPluginAutoConfiguration {

private static final String CURRENT_NAMESPACE = LambdaHelper.getPropertyName(TableInfo::getCurrentNamespace);

/**
* <p>defaultSqlInjector.</p>
*
* @return a {@link com.baomidou.mybatisplus.core.injector.DefaultSqlInjector} object
*/
@Bean
@Order
@ConditionalOnMissingBean(DefaultSqlInjector.class)
public DefaultSqlInjector defaultSqlInjector() {
return new DefaultSqlInjector() {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
methodList.add(new SaveOneSql(SqlMethodEnum.SAVE_ONE_SQL.getMethod()));
methodList.add(new UpdateOneSql(SqlMethodEnum.UPDATE_ONE_SQL.getMethod()));
return methodList;
}

@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
super.inspectInject(builderAssistant, mapperClass);
Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
if (modelClass == null) {
return;
}
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
if (Database.isDynamicMapper(tableInfo.getCurrentNamespace()) &&
!mapperClass.getName().equals(tableInfo.getCurrentNamespace())) {
// 降低动态mapper优先级
ReflectHelper.setFieldValue(tableInfo, CURRENT_NAMESPACE, mapperClass.getName());
}
if (!Database.isDynamicMapper(mapperClass.getName())) {
Database.getEntityMapperClassCache().put(modelClass, mapperClass);
}
}
};
}

}

这里重写了inspectInject方法,判断了当前如果tableInfo内存入的是动态mapper,且两个mapper的类名不一致,则使用反射修改掉tableInfocurrentNamespace

进而使得优先获取到的是项目中的Mapper

相关的单元测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
@SuppressWarnings("unchecked")
void testMapperPriority() {
try (SqlSession sqlSession = SqlHelper.sqlSession(UserInfo.class)) {
IMapper<UserInfo> userMapper = Database.getMapper(UserInfo.class, sqlSession);
MybatisMapperProxy<UserInfoMapper> userMapperProxy =
(MybatisMapperProxy<UserInfoMapper>) Proxy.getInvocationHandler(userMapper);
Class<UserInfoMapper> userMapperClass = ReflectHelper.getFieldValue(userMapperProxy, "mapperInterface");
Assertions.assertEquals(UserInfoMapper.class, userMapperClass);
Assertions.assertFalse(Database.isDynamicMapper(userMapperClass.getName()));

IMapper<RoleInfo> roleMapper = Database.getMapper(RoleInfo.class, sqlSession);
MybatisMapperProxy<? super IMapper<RoleInfo>> roleMapperProxy =
(MybatisMapperProxy<? super IMapper<RoleInfo>>) Proxy.getInvocationHandler(roleMapper);
Class<? super IMapper<RoleInfo>> roleMapperClass = ReflectHelper.getFieldValue(roleMapperProxy, "mapperInterface");
Assertions.assertTrue(Database.isDynamicMapper(roleMapperClass.getName()));
}

}

此时的UserInfoMapper是项目中存在的

因为直接使用Database.getMapper获取到的是一个代理,所以使用了Proxy.getInvocationHandler(userMapper)以及反射获取mapperInterface属性来得到代理到的目标类型

两个mapper通过isDynamicMapper方法的对比:

不是动态Mapper

1
Assertions.assertFalse(Database.isDynamicMapper(userMapperClass.getName()));

是动态Mapper

1
Assertions.assertTrue(Database.isDynamicMapper(roleMapperClass.getName()));