前言
这是最近 同事碰到的一个问题
感觉 有一些 奇怪, 然后 溜了一下 整个流程, 这里 记录一下
这是一个 mybatis-plus 组合上 mybatis-plus-join 出现的一个问题, 而且 还有一个需要的因素 就是 mybatis-plus-join 需要在 1.2.2 以及以下
测试用例
pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join</artifactId>
<version>1.2.2</version>
</dependency>
UserService
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
}
MyBatisConfig
@Bean
@Order(2)
public MybatisPlusInterceptor mybatisPlusInterceptor() {
return new MybatisPlusInterceptor();
}
// @Bean("com.github.yulichang.interceptor.MPJInterceptor")
// @Order(-2)
// public MPJInterceptor mpjInterceptor() {
// return new MPJInterceptor();
// }
MyCommandLIneRunner
/**
* MyCommandLIneRunner
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2022-06-19 10:43
*/
@Configuration
public class MyCommandLIneRunner implements CommandLineRunner {
@Resource
private UserService userService;
@Override
public void run(String... args) throws Exception {
int x = 0;
User byId = userService.getById("ff8080817c07f871017c07f87adb0000");
List<User> all = userService.list();
}
}
然后 错误日志如下
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-06-19 12:09:45.488 [main] ERROR o.s.boot.SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
at com.hx.boot.HelloSpringBootApplication.main(HelloSpringBootApplication.java:14)
Caused by: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'paramType_Gr8re1Ee' not found. Available parameters are [ew, param1]
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
at com.sun.proxy.$Proxy83.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:166)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:77)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy84.selectList(Unknown Source)
at com.baomidou.mybatisplus.extension.service.IService.list(IService.java:370)
at com.baomidou.mybatisplus.extension.service.IService.list(IService.java:379)
at com.baomidou.mybatisplus.extension.service.IService$$FastClassBySpringCGLIB$$f8525d18.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:685)
at com.hx.boot.service.UserService$$EnhancerBySpringCGLIB$$9fd70305.list(<generated>)
at com.hx.boot.config.MyCommandLIneRunner.run(MyCommandLIneRunner.java:28)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:790)
... 5 common frames omitted
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'paramType_Gr8re1Ee' not found. Available parameters are [ew, param1]
at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:212)
at org.apache.ibatis.scripting.xmltags.DynamicContext$ContextAccessor.getProperty(DynamicContext.java:120)
at org.apache.ibatis.ognl.OgnlRuntime.getProperty(OgnlRuntime.java:3341)
at org.apache.ibatis.ognl.ASTProperty.getValueBody(ASTProperty.java:121)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
at org.apache.ibatis.ognl.ASTNotEq.getValueBody(ASTNotEq.java:50)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
at org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:61)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:586)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:550)
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46)
at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:32)
at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:34)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:305)
at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:69)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:62)
at com.sun.proxy.$Proxy95.query(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
... 20 common frames omitted
问题的调试
case1, 可以看到的是 来解析 sqlSource 中的 sqlNode 出现问题, 第四个 ifNode, 中参数 "paramType_Gr8re1Ee" 在参数的上下文中不存在
case2, 我们来看一下 没有 导入 mybatis-plus-join 的依赖的情况下 的查询情况, 因为没有导入 mybatis-plus-join 的情况下 执行是没有问题的
可以看到的是 少了几个 Node, 没有我们上面的包含 "paramType_Gr8re1Ee" 的那个 IfNode
case3, 我们来看一下 项目完全 的查询情况, 因为在 applicationContext 完整加载了之后, 执行是没有问题的
可以看到的是 这里有完整的 sqlSource 的 sqlNode 有完整的 12 个节点
然后 参数中补充了一个参数 "paramType_Gr8re1Ee", 即为这里 需要的参数, 因此 没有报错
上面的两个参照会映射出一些问题
1. 根据 case1 和 case2 的对比, 可以发现的是 mbatis-plus-join 对于查询 sql 的处理增加了一些 辅助的语句, 包含了这里出现问题的 "paramType_Gr8re1Ee"参数相关
2. 根据 case1 和 case3 对比, applicationContext 完全加载之后, 查询的时候会在 参数中增加这个 "paramType_Gr8re1Ee"?, 当然 如果你够仔细的话 你可以从 case1 和 case3 的图中发现一些问题, 差异, 这些细节差异 造成了 这个问题
3. "paramType_Gr8re1Ee" 的 sql 语句是从哪里来的?, 然后 参数"paramType_Gr8re1Ee"是从哪里填充的?
我们从 后面 来剖析这个问题的时候, 会解释出来 以上这些问题
哪里增加的 "paramType_Gr8re1Ee" 这一系列语句?
MPJInjector 注入各个预制方法的时候, 一部分方法 的 tableName 是有更新
这个 tableName 的实现来自于 TableAlias 的接口, 他有如下实现, 因此 如下 预制方法 均会出现这个问题
mybatis-plus-join 的 selectList 的实现大致如下, 使用的 SqlInjector 是 MPJSqlInector
如果是仅仅只有 mybatis-plus, selectList 的实现大致如下, 使用的 SqlInjector 是 DefaultSqlInector
applicationContext 初始化好了之后 哪里增加的参数 "paramType_Gr8re1Ee" ?
这个参数 "paramType_Gr8re1Ee" 的添加是在 MPJInterceptor 中添加
另外注意 "当然 如果你够仔细的话 你可以从 case1 和 case3 的图中发现一些问题, 差异, 这些细节差异 造成了 这个问题", 注意 看这里的堆栈信息
出现问题的堆栈信息, 是 MybatisPlusInterceptor, 而 正常的堆栈信息是 MPJInterceptor -> MybatisPlusInterceptor
而参数 "paramType_Gr8re1Ee" 是 MPJInterceptor 添加进去的, 相当于使用的是 "MPJInterceptor" 生成的 sql, 但是 却没有使用 MPJInterceptor 来包装参数列表, 导致缺少了参数 "paramType_Gr8re1Ee"
这里会 引申出关键的问题, 这个 interceptor列表 的执行到底是怎么回事 为什么 是在变化? 变化的规则到底是什么?
这个 interceptor列表 的执行到底是怎么回事 为什么 是在变化? 变化的规则到底是什么?
看正常情况下 DefaultSession 中的 executor, 外层 interceptor 是 MPJInterceptor, 内层 interceptor 是 MybatisDefaultInterceptor, 再内层是 具体的 executor 是一个 CachingExecutor 委托了一个 SImpleExecutor 来具体做事情
出现问题的情况下
看正常情况下 DefaultSession 中的 executor, 外层 interceptor 是 MybatisDefaultInterceptor, 内层 interceptor 是 MPJInterceptor, 再内层是 具体的 executor 是一个 CachingExecutor 委托了一个 SImpleExecutor 来具体做事情
我们这里来探究一下 这个流程
session 的创建流程, 以及相关影响因素, 这里 executor 受到了 interceptorChain 的影响
interceptorChain 来自于 sessionFactory, sessionFactory 的 interceptor 来自于 sessionFactoryBean
sessionFactoryBean 来自于 MybatisPlusAutoConfiguration 中创建的
MybatisPlusAutoConfiguration 中的 interceptor 列表来自于 applicationContext 中查询到 Interceptor 列表, 然后 进行排序
排序来自于 @Order 或者 @Priority 默认为 Integer.MAX_VALUE
MPJInterceptor 在 mybatis-plus-join 1.2.2 中 @Order 为 -2147483648, MybatisPlusInterceptor 我这里随便配置的一个 2
排序之后, MPJInterceptor 放在了 MybatisPlusInterceptor 之前
然后创建了 代理之后, 形成的调用链就是 MybatisPlusInterceptor -> MPJInterceptor -> CachingExecutor -> SimpleExecutor, 也就是 出现问题的调用链
在 applicationContext 加载完成之后 com.github.yulichang.config.InterceptorConfig 监听 applicationContext 加载完成的事件, 然后将 MPJInterceptor 放到 interceptor 列表的最后一个
然后根据 openSessoin 创建出来的代理, 形成的调用链就是 MPJInterceptor -> MybatisPlusInterceptor -> CachingExecutor -> SimpleExecutor
这个就是 applicationContext 加载完成之后, 正常调用 selectList 的情况
回溯一下这几个问题?
1. 根据 case1 和 case2 的对比, 可以发现的是 mbatis-plus-join 对于查询 sql 的处理增加了一些 辅助的语句, 包含了这里出现问题的 "paramType_Gr8re1Ee"参数相关
2. 根据 case1 和 case3 对比, applicationContext 完全加载之后, 查询的时候会在 参数中增加这个 "paramType_Gr8re1Ee"?, 当然 如果你够仔细的话 你可以从 case1 和 case3 的图中发现一些问题, 差异, 这些细节差异 造成了 这个问题
3. "paramType_Gr8re1Ee" 的 sql 语句是从哪里来的?, 然后 参数"paramType_Gr8re1Ee"是从哪里填充的?
MPJInterceptor 是怎么创建的?
来自于 AutoImport
扫描 mybatis-plus-join-1.2.2.jar!/META-INF/spring.factories 的时候, 注册了这个 ConfigraionClass
jar:file:/Users/jerry/.m2/repository/org/springframework/boot/spring-boot/2.0.0.RELEASE/spring-boot-2.0.0.RELEASE.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/org/springframework/spring-beans/5.0.4.RELEASE/spring-beans-5.0.4.RELEASE.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/org/springframework/data/spring-data-jpa/2.0.5.RELEASE/spring-data-jpa-2.0.5.RELEASE.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/org/springframework/data/spring-data-commons/2.0.5.RELEASE/spring-data-commons-2.0.5.RELEASE.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/com/baomidou/mybatis-plus-boot-starter/3.5.2/mybatis-plus-boot-starter-3.5.2.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.0.0.RELEASE/spring-boot-autoconfigure-2.0.0.RELEASE.jar!/META-INF/spring.factories
jar:file:/Users/jerry/.m2/repository/com/github/yulichang/mybatis-plus-join/1.2.2/mybatis-plus-join-1.2.2.jar!/META-INF/spring.factories
后面向 applicationContext 中注册了这个 beanDefinition
完