mybatisPlus拦截sql失败----已解决

1. 在使用MybatisPlusInterceptor想对数据权限做限制,但是发现使用寻常的配置,在执行sql后也不会被拦截 代码如下:

@Configuration public class MybatisPlusConfig { public MybatisPlusInterceptor setMybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataPermissionHandler())); return interceptor; }
}

执行sql语句后拦截器没有反应

@Slf4j
public class MyDataPermissionHandler implements DataPermissionHandler {

    @Override
    @SneakyThrows
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        // 超级管理员不受数据权限控制

        Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringPool.DOT)));
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + 1);
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            DataPermission annotation = method.getAnnotation(DataPermission.class);
            if (ObjectUtils.isNotEmpty(annotation)
                    && (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {

                return dataScopeFilter(annotation.dataScope(), annotation.deptAlias(), annotation.deptIdColumnName(), annotation.userAlias(), annotation.userIdColumnName(), where);
            }
        }
        return where;
    }


    /**
     * 构建过滤条件
     *
     * @param where 当前查询条件
     * @return 构建后查询条件
     */
    @SneakyThrows
    public static Expression dataScopeFilter(DataPermissionEnum dataScopeEnum, String deptAlias, String deptIdColumnName, String userAlias, String userIdColumnName, Expression where) {


        String deptColumnName = StrUtil.isNotBlank(deptAlias) ? (deptAlias + StringPool.DOT + deptIdColumnName) : deptIdColumnName;
        String userColumnName = StrUtil.isNotBlank(userAlias) ? (userAlias + StringPool.DOT + userIdColumnName) : userIdColumnName;

        TspUserInfo tspUserInfo = ThreadContext.getSubject();
        if (dataScopeEnum == null) {
            String dataPermission = tspUserInfo.getDataPermission();
            // 获取当前用户的数据权限
            dataScopeEnum = DataPermissionEnum.getByCode(dataPermission);
        }

        //部门ID 包含多个部门,同一个用户的部门来源于用户组
        String orgCode = tspUserInfo.getOrgCode();
        String userId = tspUserInfo.getUserId();
        String acctOrgCode = tspUserInfo.getAcctOrgCode();
        List<String> dataPermissionOrgCodes = tspUserInfo.getDataPermissionOrgCodes();
        String appendSqlStr;
        switch (Objects.requireNonNull(dataScopeEnum)) {
            case Org:
//                appendSqlStr = deptColumnName + " in (" + orgCode + ")";
                appendSqlStr = deptColumnName + StringPool.EQUALS + orgCode;
                break;
            case Personal:
                appendSqlStr = userColumnName + StringPool.EQUALS + userId;
                break;
            case AcctOrg:
                appendSqlStr = userColumnName + StringPool.EQUALS + acctOrgCode;
                break;
            // 默认部门及子部门数据权限
            default:
                appendSqlStr = deptColumnName + " in (" + String.join(",", dataPermissionOrgCodes) + ")";
                break;
        }


        if (StrUtil.isBlank(appendSqlStr)) {
            return where;
        }

        Expression appendExpression = CCJSqlParserUtil.parseCondExpression(appendSqlStr);

        if (where == null) {
            return appendExpression;
        }

        return new AndExpression(where, appendExpression);
    }


}

2. 就认为是拦截器没有起作用,后面同事大哥给了一个解决方法

@Slf4j
public class SqlSessionFactoryBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof SqlSessionFactory) { //所有bean初始化之后都会进入这个方法,这个时候需要滤出需要的类型,比如这次就只需要拿到SqlSessionFactory类型的对象对其设置拦截器就行了
            SqlSessionFactory nowBean = (SqlSessionFactory) bean;
            nowBean.getConfiguration().addInterceptor(new RowVersionUpdateInterceptor());
            nowBean.getConfiguration().addInterceptor(setMybatisPlusInterceptor());
        }
        return bean; //完成后返回出去,可能直接进入容器,也可能会去执行其他的BeanPostProcessor
    }
    public MybatisPlusInterceptor setMybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataPermissionHandler()));
        return interceptor;
    }
}

直接在注册Bean的时候,将SqlSessionFactory相关的bean都加上拦截器,然后再把SqlSessionFactoryBeanPostProcessor 注册到ioc中

@Configuration
public class MybatisPlusConfig {

    @Bean
    public SqlSessionFactoryBeanPostProcessor sqlSessionFactoryBeanPostProcessor() {
        return new SqlSessionFactoryBeanPostProcessor();
    }
}

3.可以解决问题,但是又不知道这个问题出在哪里,因为我之前都是这样配的,且重新搭建一个新的环境按照原来的方法依然可以,等我了解后问题,在来加更。。。。。。

<think>我们需要使用MyBatis-Plus的InnerInterceptor接口来拦截SQL并动态替换表名。根据引用[1]的描述,InnerInterceptor提供了多个拦截点,其中与SQL语句准备相关的拦截点是beforePrepare和afterPrepare。考虑到修改SQL语句,通常在准备阶段进行,这里我们选择在beforePrepare方法中修改SQL。 具体步骤: 1. 创建一个自定义拦截器类,实现InnerInterceptor接口。 2. 重写beforePrepare方法,在该方法中获取到即将执行的SQL语句,然后修改其中的表名。 3. 替换表名的逻辑可以根据具体需求,例如根据线程上下文、时间等条件动态改变表名。 注意:MyBatis-Plus的拦截器机制允许我们在SQL执行前修改SQL语句。在beforePrepare方法中,我们可以通过StatementHandler获取到BoundSql,从而获取SQL字符串,然后使用字符串替换或者更精确的SQL解析工具(如JSqlParser)来替换表名。但考虑到简单替换可能带来的风险(如表名与其他单词混淆),建议使用SQL解析器。 然而,在MyBatis-Plus中,如果只是简单的表名替换(如分表场景,将表名替换为带有后缀的表名),且表名在SQL中出现的格式固定(比如用反引号包裹或特定前缀),也可以使用字符串替换。但为了更准确,我们这里使用MyBatis-Plus提供的SQL解析工具(如果有)或者使用JSqlParser。 不过,MyBatis-Plus本身并没有内置SQL解析器,所以我们需要引入JSqlParser(如果项目允许)或者使用字符串替换(简单场景)。 这里我们给出两种方式的思路: 方式一:使用字符串替换(简单,但容易出错) 假设我们原始表名为`user`,现在要替换为`user_2023`(动态表名),那么我们可以将SQL中的`user`替换为`user_2023`。但注意,要避免替换到其他地方出现的`user`,比如字段名或字符串值。因此,我们可以通过给表名添加特定标记(如前后加空格)或者使用反引号(如果SQL中表名都用反引号包裹)来确保准确性。 方式二:使用JSqlParser(准确,但需要额外依赖) 1. 引入JSqlParser依赖(如:com.github.jsqlparser:jsqlparser:4.2) 2. 在拦截器中,使用JSqlParser解析SQL,然后遍历找到表名,替换为新的表名,再重新生成SQL。 考虑到准确性和通用性,我们推荐使用JSqlParser。 下面给出使用JSqlParser的实现示例: 步骤: 1. 在pom.xml中加入JSqlParser依赖: ```xml <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>4.2</version> </dependency> ``` 2. 编写自定义拦截器: ```java import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitorAdapter; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.schema.Table; import org.apache.ibatis.executor.statement.StatementHandler; import java.sql.Connection; public class DynamicTableNameInterceptor implements InnerInterceptor { @Override public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) { // 获取BoundSql,里面包含SQL语句 String sql = sh.getBoundSql().getSql(); // 使用JSqlParser解析并修改表名 String newSql = replaceTableName(sql); // 将修改后的SQL重新设置回BoundSql sh.getBoundSql().setSql(newSql); } private String replaceTableName(String sql) { try { Statement statement = CCJSqlParserUtil.parse(sql); // 根据不同的语句类型,使用不同的访问者修改表名 if (statement instanceof Select) { ((Select) statement).getSelectBody().accept(new TableNameVisitor()); } else if (statement instanceof Update) { ((Update) statement).accept(new TableNameVisitor()); } else if (statement instanceof Delete) { ((Delete) statement).accept(new TableNameVisitor()); } else if (statement instanceof Insert) { ((Insert) statement).accept(new TableNameVisitor()); } return statement.toString(); } catch (Exception e) { // 解析失败,返回原始SQL return sql; } } // 自定义访问者,用于遍历SQL中的表并替换表名 private static class TableNameVisitor extends StatementVisitorAdapter { @Override public void visit(Table table) { // 这里根据实际需求动态获取新表名,例如从线程变量中获取 String tableName = table.getName(); String newTableName = getDynamicTableName(tableName); // 实现动态获取表名的方法 table.setName(newTableName); } private String getDynamicTableName(String originalTableName) { // 这里实现你的动态表名逻辑,例如按月分表,则返回 originalTableName + "_" + 月份 // 示例:将表名替换为 originalTableName + "_202301" return originalTableName + "_202301"; } } } ``` 3. 注册拦截器到MyBatis-Plus的拦截器链: 在MyBatis-Plus配置类中: ```java @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new DynamicTableNameInterceptor()); return interceptor; } } ``` 注意:上述示例中,我们简单地将表名后面添加了一个固定的后缀 "_202301"。实际应用中,你需要根据业务场景(如分表策略)动态生成表名。可以在`getDynamicTableName`方法中实现,比如从ThreadLocal中获取当前线程设定的表名后缀,或者根据时间生成等。 另外,上述代码仅处理了常见的SELECT, UPDATE, DELETE, INSERT语句。如果需要处理其他类型的语句(如CREATE等),则需要扩展。 使用拦截器动态替换表名的方法如上所述。在实际应用中,请确保表名替换逻辑的健壮性,避免SQL解析失败导致的问题。 最后,注意JSqlParser的版本和兼容性,以及性能影响(解析SQL会带来一定的性能开销)。 [^1]: 引用内容描述了InnerInterceptor的基本作用和拦截点,为我们的实现提供了依据。 </think>### MyBatis-Plus InnerInterceptor 拦截 SQL 实现动态表名替换 在 MyBatis-Plus 中使用 `InnerInterceptor` 动态替换表名,主要通过拦截 SQL 准备阶段实现。以下是具体实现方法: #### 1. **创建自定义拦截器** 实现 `InnerInterceptor` 接口,重写 `beforePrepare` 方法(在 SQL 准备前拦截): ```java import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import java.sql.Connection; import java.util.regex.Pattern; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; public class DynamicTableInterceptor implements InnerInterceptor { // 原始表名 -> 动态表名的映射规则(根据业务需求实现) private String getDynamicTableName(String originalTable) { // 示例:按月分表(user -> user_202305) return originalTable + "_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")); } @Override public void beforePrepare(StatementHandler handler, Connection conn, Integer transactionTimeout) { BoundSql boundSql = handler.getBoundSql(); String originalSql = boundSql.getSql(); // 使用正则匹配替换表名(假设原始表名为 "user") String dynamicSql = Pattern.compile("\\buser\\b", Pattern.CASE_INSENSITIVE) .matcher(originalSql) .replaceAll(getDynamicTableName("user")); // 反射修改 SQL ReflectUtil.setFieldValue(boundSql, "sql", dynamicSql); } } ``` #### 2. **注册拦截器** 在 MyBatis-Plus 配置类中添加拦截器: ```java @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new DynamicTableInterceptor()); return interceptor; } } ``` #### 关键实现细节 1. **拦截时机** 选择 `beforePrepare` 阶段(在 SQL 编译完成但未执行前修改)[^1] 2. **表名替换逻辑** - 正则替换:`\\buser\\b` 确保精确匹配表名(避免替换字段名) - 动态规则:在 `getDynamicTableName()` 中实现分表逻辑(如按时间、租户等) 3. **反射修改 SQL** 通过反射修改 `BoundSql` 中的 SQL 字符串(因 MyBatis 未提供直接修改 API) #### 注意事项 1. **多表处理** 若需处理多张表,可扩展正则表达式: ```java Map<String, String> tableMappings = Map.of("user", getDynamicTableName("user"), "order", getDynamicTableName("order")); // 遍历替换所有表名 for (Map.Entry<String, String> entry : tableMappings.entrySet()) { sql = sql.replaceAll("\\b" + entry.getKey() + "\\b", entry.getValue()); } ``` 2. **SQL 解析建议** 对复杂 SQL(如子查询、别名),建议使用 **JSqlParser** 精准替换: ```java Statement stmt = CCJSqlParserUtil.parse(sql); stmt.accept(new TableNameVisitor() { // 自定义访问者替换表名 public void visit(Table table) { table.setName(getDynamicTableName(table.getName())); } }); ``` 3. **性能影响** - 简单正则替换:性能开销小 - SQL 解析器:更准确但影响性能,建议仅在复杂场景使用 [^1]: 引用内容确认了 `beforePrepare` 是修改 SQL 的最佳时机。 --- ### 相关问题 1. **如何实现基于租户 ID 的动态表名分片?** 2. **使用 JSqlParser 替换表名时如何避免 SQL 语法错误?** 3. **MyBatis-Plus 拦截器能否修改查询参数?具体如何实现?**
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘个Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值