当前使用版本(必填,否则不予处理)
mybatis-plus v3.5.1
该问题是如何引起的?(确定最新版也有问题再提!!!)
请参考原来的问题 https://github.com/baomidou/mybatis-plus/issues/4488 因为这个问题已经被设置为closed,所以重开一个问题
重现步骤(如果有就写完整)
首先感谢“miemieYaho”的快速回复,但针对回复的内容“association 配置的 select 查询是任何插件都不会走的”我们不太认可,因为我们恢复到mybatis-plus v3.2.0,然后把TenantLineInnerInterceptor改回旧版本的TenantSqlParser,对应的association中对应的子查询是可以正常添加TenantIdColumn "CID",如下恢复旧版的log所示:
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53c2c402] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@75d7297d] will not be managed by Spring
Original SQL: SELECT
USERID,SESSIONID,OPER_HOST,OPER_BROWSER,OPER_OS,LOGIN_DATE,LAST_OP_TIME
FROM T_AT_SESSION
ORDER BY LOGIN_DATE DESC , USERID ASC
parser sql: SELECT USERID, SESSIONID, OPER_HOST, OPER_BROWSER, OPER_OS, LOGIN_DATE, LAST_OP_TIME FROM T_AT_SESSION WHERE T_AT_SESSION.CID = 1 ORDER BY LOGIN_DATE DESC, USERID ASC
==> Preparing: SELECT USERID, SESSIONID, OPER_HOST, OPER_BROWSER, OPER_OS, LOGIN_DATE, LAST_OP_TIME FROM T_AT_SESSION WHERE T_AT_SESSION.CID=1 AND USERID IN(SELECT USERID FROM T_AT_SESSION WHERE T_AT_SESSION.CID = 1 ORDER BY LOGIN_DATE DESC, USERID ASC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY) ORDER BY LOGIN_DATE DESC, USERID ASC
==> Parameters: 0(Long), 10(Long)
<== Columns: USERID, SESSIONID, OPER_HOST, OPER_BROWSER, OPER_OS, LOGIN_DATE, LAST_OP_TIME
<== Row: 68d1631efd519da694f3fe452b5d4242, 8097ff01-336a-453f-ba4a-f9d349b644bd, 127.0.0.1, Chrome 9, Windows 10, 2022-05-10 13:31:03, 2022-05-10 13:35:33.000949
Original SQL: SELECT * FROM T_USER WHERE USERID=?
parser sql: SELECT * FROM T_USER WHERE USERID = ? AND T_USER.CID = 1
====> Preparing: SELECT * FROM T_USER WHERE USERID = ? AND T_USER.CID = 1
====> Parameters: 68d1631efd519da694f3fe452b5d4242(String)
<==== Columns: USERID, CID, NAME, LOGIN_NAME, SALT, PASSWORD, EMAIL, PHONE, MOBILE, FAX, ADDRESS, FAIL_TIMES, INIT_PWD, STATUS, LOCK_REASON, PWD_MODIFY_DATE, HISTORY_PWD, LAST_LOGIN_DATE, CREATOR_ID, CREATE_DATE, MODIFIER_ID, LAST_MODIFY_DATE, LAST_ROLE, VERSION
<==== Row: 68d1631efd519da694f3fe452b5d4242, 1, wing, wing, ac09f1adca4fcc26, 90dbe50a06cb254ef89bbc59d1276c119259ea438685f442949ca8e46a0b2a70, wing.li@lyodssoft.com, 110000, 120000000, 1300000, 1500, 0, 0, 0, null, 2021-10-20 11:53:49, ac09f1adca4fcc2690dbe50a06cb254ef89bbc59d1276c119259ea438685f442949ca8e46a0b2a70, 2022-05-10 13:31:03, 1, 2021-10-20 11:53:30, 1, 2021-12-03 16:28:14, 599571c6094435f813e5acb851a15357, 103
Original SQL: SELECT * FROM T_USER WHERE USERID=?
parser sql: SELECT * FROM T_USER WHERE USERID = ? AND T_USER.CID = 1
======> Preparing: SELECT * FROM T_USER WHERE USERID = ? AND T_USER.CID = 1
======> Parameters: 1(String)
<====== Columns: USERID, CID, NAME, LOGIN_NAME, SALT, PASSWORD, EMAIL, PHONE, MOBILE, FAX, ADDRESS, FAIL_TIMES, INIT_PWD, STATUS, LOCK_REASON, PWD_MODIFY_DATE, HISTORY_PWD, LAST_LOGIN_DATE, CREATOR_ID, CREATE_DATE, MODIFIER_ID, LAST_MODIFY_DATE, LAST_ROLE, VERSION
<====== Row: 1, 1, administrator, admin, 247b29c80c634031, 5496d0d23e0d4900af586cd8edfdea67c9ff3b2feffbe42d6bc4df00527bb24f, null, null, null, null, null, 0, 0, 0, 1, 2021-06-15 18:47:02, 247b29c80c634031f4b75c3c1bbfb568b89e882240e46d6f388f64e1a3e1e8bd895716ef31510a8e, 2022-03-22 18:15:27, 1, 2013-10-10 12:00:00, 1, 2013-10-10 12:00:00, 1, 193
<====== Total: 1
<==== Total: 1
<== Row: f0595d136a9657bd5610d29397a2ac22, dc80e9b7-5313-41c2-83a7-0027c2d1c7b7, 127.0.0.1, Chrome 55, Windows 10, 2022-05-10 13:27:49, 2022-05-10 13:28:35.000813
报错信息
从 v3.2.0的log我们可以看到如下信息: Original SQL: SELECT * FROM T_USER WHERE USERID=? parser sql: SELECT * FROM T_USER WHERE USERID = ? AND T_USER.CID = 1 即,证明旧版本3.2.0是可以支持association中对应的子查询可以正常添加TenantIdColumn "CID"
Comment From: kevinluoc
我们确认拦截器是肯定可以拦截到association中配置SQL的,因为我们继承的是mybatis中的org.apache.ibatis.plugin.Interceptor,我们自己尝试重写mybatis-plus中的MybatisPlusInterceptor.java方法“ intercept(Invocation invocation)”,增加“if (target instanceof Executor) {”中对应的else分支,是可以解决的,具体如下所示:
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
Object[] args = invocation.getArgs();
if (target instanceof Executor) {
final Executor executor = (Executor) target;
Object parameter = args[1];
boolean isUpdate = args.length == 2;
MappedStatement ms = (MappedStatement) args[0];
if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT) {
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
BoundSql boundSql;
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
} else {
// 几乎不可能走进这里面,除非使用Executor的代理对象调用query[args[6]]
boundSql = (BoundSql) args[5];
}
System.err.println("select---<>:" + boundSql);
for (InnerInterceptor query : interceptors) {
if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
return Collections.emptyList();
}
query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} else if (isUpdate) {
for (InnerInterceptor update : interceptors) {
if (!update.willDoUpdate(executor, ms, parameter)) {
return -1;
}
update.beforeUpdate(executor, ms, parameter);
}
}
} else {
// StatementHandler
final StatementHandler sh = (StatementHandler) target;
// 目前只有StatementHandler.getBoundSql方法args才为null
if (null == args) {
for (InnerInterceptor innerInterceptor : interceptors) {
innerInterceptor.beforeGetBoundSql(sh);
}
} else {
Connection connections = (Connection) args[0];
Integer transactionTimeout = (Integer) args[1];
for (InnerInterceptor innerInterceptor : interceptors) {
innerInterceptor.beforePrepare(sh, connections, transactionTimeout);
}
}
// 增加association的SQL處理
BoundSql boundSql = sh.getBoundSql();
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
if (ms.getSqlCommandType() == SqlCommandType.SELECT) {
for (InnerInterceptor query : interceptors) {
if (!query.willDoQuery(null, ms, null, null, null, boundSql)) {
return Collections.emptyList();
}
query.beforeQuery(null, ms, null, null, null, boundSql);
}
}
}
return invocation.proceed();
}
麻烦确认一下官方是否可以修改这一块内容,谢谢!
Comment From: huayanYu
欢迎 直接PR
Comment From: miemieYaho
InnerInterceptor 各个method都有对应的场景,你这种乱用法是不合适的
Comment From: kevinluoc
InnerInterceptor 各个method都有对应的场景,你这种乱用法是不合适的
谢谢,如果想要实现之前v3.2.0那种效果,需要如何实现比较合适,有没有什么建议?
Comment From: miemieYaho
根据test来看是能每次都进beforePrepare的,具体的你自己debug看看吧
Comment From: jimmy384
我也遇到这个问题,版本是3.4.3.4,贴一下我的解决方法
import cn.hutool.core.bean.BeanUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import java.sql.Statement;
import java.util.List;
@Intercepts(
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
)
public class NestedQueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof ResultSetHandler) {
ResultSetHandler resultSetHandler = (ResultSetHandler) target;
InnerObject innerObject = extractInnerObject(resultSetHandler);
boolean hasNestedQuery = hasNestedQuery(innerObject.mappedStatement);
if (hasNestedQuery) {
/*
* Mybatis嵌套查询的流程
* 1.查询(Executor.query)
* |-2.封装结果(ResultSetHandler)
* |-3.如果2中的resultMap定义了嵌套查询, 这里会再触发一次查询(Executor.query)
*
* 第1步中的executor是经过interceptor代理的对象
* 第3步中的executor是原生对象,没有经过interceptor代理
* 所以MybatisPlusInterceptor对嵌套查询不起作用,MybatisPlusInterceptor里面的DynamicTableNameInnerInterceptor自然也不起作用
*
* 现在想DynamicTableNameInnerInterceptor起作用, 这里把第3步的executor替换成经过interceptor代理的代理对象
*/
Object executor = pluginAll(innerObject.executor, innerObject.configuration.getInterceptors());
BeanUtil.setProperty(target, "executor", executor);
}
}
return target;
}
private boolean hasNestedQuery(MappedStatement mappedStatement) {
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
boolean hasNestedQuery = false;
if (CollectionUtils.isNotEmpty(resultMaps)) {
for (ResultMap resultMap : resultMaps) {
if (resultMap.hasNestedQueries()) {
hasNestedQuery = true;
break;
}
}
}
return hasNestedQuery;
}
private Object pluginAll(Object target, List<Interceptor> interceptors) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
private InnerObject extractInnerObject(ResultSetHandler resultSetHandler) {
InnerObject innerObject = new InnerObject();
innerObject.configuration = BeanUtil.getProperty(resultSetHandler, "configuration");
innerObject.executor = BeanUtil.getProperty(resultSetHandler, "executor");
innerObject.mappedStatement = BeanUtil.getProperty(resultSetHandler, "mappedStatement");
return innerObject;
}
private static class InnerObject {
Configuration configuration;
Executor executor;
MappedStatement mappedStatement;
}
}
Comment From: Hccake
可以使用 h2 数据库提供一个最简的复现 demo
Comment From: qmdx
升级 3.5.3.2 后测试,还是未解决打开该问题反馈错误