MybatisPlus自定义拦截器修改sql

本文介绍如何使用MybatisPlus自定义拦截器修改SQL查询条件,包括直接拼接SQL和预编译方式,提供五个示例拦截器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用MybatisPlus自定义拦截器,修改sql的where条件。工作需要,上网找教程没有太符合需求的。于是自己盘了一下代码,在此也记录一下。
MybatisPlus简称MP,基于Mybatis的又封装,底层还是Mybatis的东西。想要玩转,还是要先看看Mybatis的拦截器处理机制。工作项目使用的是MP,所以目前是基于MP的拦截器(InnerInterceptor)进行处理的。(当然也可以使用Mybatis的拦截器,这种方式自行百度吧。)
基于MP的拦截器“InnerInterceptor”,分为两种方式处理:
A:直接拼接sql方式,最简单。
B:使用预编译的方式,复杂一些(防止sql注入)。
一共分为4个演示的拦截器,QueryInterceptor1(直接拼sql)、QueryInterceptor2(直接拼sql)、QueryInterceptor3(预编译)、QueryInterceptor4(预编译)。
Demo项目已上传至Gitee:https://gitee.com/hechuan_song/springboot-mp
补充一下:获取sql传入的参数可以查看QueryInterceptor5
继续补充:更优雅的拼接sql查看QueryInterceptor6

一.数据准备,一张简单的用户表

在这里插入图片描述

二.创建MybatisPlus配置类

测试时,不要忘记放开对应的拦截器的注释。其它的拦截器则需要注释掉。

package com.hechuan.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.hechuan.interceptor.QueryInterceptor1;
import com.hechuan.interceptor.QueryInterceptor2;
import com.hechuan.interceptor.QueryInterceptor3;
import com.hechuan.interceptor.QueryInterceptor4;
import com.hechuan.interceptor.QueryInterceptor5;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MybatisPlusConfig
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new QueryInterceptor1());
//        interceptor.addInnerInterceptor(new QueryInterceptor2());
//        interceptor.addInnerInterceptor(new QueryInterceptor3());
//        interceptor.addInnerInterceptor(new QueryInterceptor4());
//        interceptor.addInnerInterceptor(new QueryInterceptor5());
//        interceptor.addInnerInterceptor(new QueryInterceptor6());
        return interceptor;
    }

}

三.单元测试类

package com.hechuan;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hechuan.doman.User;
import com.hechuan.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

/**
 * UserTest
 */
@SpringBootTest
public class UserTest {

    @Autowired
    private UserService userService;

    @Test
    void selectUser() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("age", 18);
        // queryWrapper.eq("name", "张三");

        List<User> list = userService.list(queryWrapper);
        System.out.println(list);
        System.out.println("end");
    }

}

四.自定义拦截器

1.QueryInterceptor1(直接拼sql)

原始sql逻辑:查询所有用户
需求:查询年龄为18岁的用户
PS:这种写法有个坑,前提是原始的查询sql没有where条件或者其它查询参数。如果有查询参数,还这么写,就会报错。原因是最终拼接sql参数时,底层会按传入的参数进行拼接,参数与占位符“?”的顺序不匹配或者找不到就导致报错(JDBC中的 PreparedStatement,设置值,按顺序设置)。这种情况,则使用QueryInterceptor2的写法。

package com.hechuan.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * 查询拦截器1
 */
public class QueryInterceptor1 implements InnerInterceptor {

    /**
     * 简单直接
     * 缺点:没有预编译sql条件 直接拼值。可能会造成sql注入
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // 修改后的sql
        String targetSql = "SELECT id,name,age,address FROM t_user WHERE age = 18";

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(targetSql);
    }

}

2.QueryInterceptor2(直接拼sql)

原始sql逻辑:查询用户为 张三,年龄为18的用户
需求:查询年龄为18岁的用户

package com.hechuan.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;

/**
 * 查询拦截器2
 */
public class QueryInterceptor2 implements InnerInterceptor {

    /**
     * 简单直接
     * 缺点:没有预编译sql条件 直接拼值。可能会造成sql注入
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user WHERE (name = ? AND age = ?)
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // 修改后的sql
        String targetSql = "SELECT id,name,age,address FROM t_user WHERE age = 18";

        // 清空绑定值的映射路径
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        parameterMappings.clear();

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(targetSql);
    }

}

3.QueryInterceptor3(预编译)

原始sql逻辑:查询用户为 张三,年龄为18的用户
需求:查询年龄为18岁的用户
PS:更进一步的详细说明查看QueryInterceptor4

package com.hechuan.interceptor;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.UnknownTypeHandler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 查询拦截器3
 */
public class QueryInterceptor3 implements InnerInterceptor {

    /**
     * 复杂一点
     * 优点:预编译sql条件
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user WHERE (name = ? AND age = ?)
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // 修改后的sql
        String targetSql = "SELECT id,name,age,address FROM t_user WHERE age = ?";

        // 清空 绑定值映射路径(这里只是清空 绑定值的 映射路径,真正的值存在 parameter对象中,ew键对应的 QueryWrapper对象中的paramNameValuePairs中)
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        parameterMappings.clear();

        // 添加绑定值 映射路径
        // 这两个参数 configuration、unknownTypeHandler,只是创建出来,并没有发现有什么用,只是让ParameterMapping.Builder(),能正常通过。
        Configuration configuration = new Configuration();
        UnknownTypeHandler unknownTypeHandler = new UnknownTypeHandler(configuration);

        // 第二个参数:ew.paramNameValuePairs.MPGENVAL1,固定写法。多个绑定值映射路径 看 QueryInterceptor4 中有说明。
        ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, "ew.paramNameValuePairs.MPGENVAL1", unknownTypeHandler);
        ParameterMapping parameterMapping = builder.build();
        // 添加绑定值 映射路径
        parameterMappings.add(parameterMapping);

        // 添加绑定值
        Map map = (Map) parameter;
        // 获取 QueryWrapper 对象
        QueryWrapper queryWrapper = (QueryWrapper) map.get("ew");

        // 通过反射获取 queryWrapper中 paramNameValuePairs的值
        // 这里使用一个工具(hutool)设置,如若直接使用反射获取QueryWrapper对象中的值会报错,找不到对应的字段
        // 因为paramNameValuePairs字段是QueryWrapper对象父级对象中的字段
        // 工具会循环向上查找
        HashMap hashMap = (HashMap)ReflectUtil.getFieldValue(queryWrapper, "paramNameValuePairs");
        // 将原有的绑定值清空
        hashMap.clear();

        // 添加新值,这里设置的key 要与 绑定值映射路径最后一级保持一致
        // 最终 18 会拼接在 sql中,例如:SELECT id,name,age,address FROM t_user WHERE age = 18
        hashMap.put("MPGENVAL1", 18);

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(targetSql);
    }

}

4.QueryInterceptor4(预编译)

原始sql逻辑:查询用户为 张三,年龄为18的用户
需求:查询年龄为18岁的用户 并且 住址是 上海 或者 北京
PS:此演示只是QueryInterceptor3的补充,原理还是一样。

package com.hechuan.interceptor;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.UnknownTypeHandler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 查询拦截器4
 */
public class QueryInterceptor4 implements InnerInterceptor {

    /**
     * 复杂一点---详细说明
     * 优点:预编译sql条件
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user WHERE (name = ? AND age = ?)
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // 修改后的sql
        String targetSql = "SELECT id,name,age,address FROM t_user WHERE age = ? and (address = ? or address = ?)";

        // 清空 绑定值映射路径(这里只是清空 绑定值的 映射路径,真正的值存在 parameter对象中,ew键对应的 QueryWrapper对象中的paramNameValuePairs中)
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        parameterMappings.clear();

        // 添加绑定值 映射路径
        // 这两个参数 configuration、unknownTypeHandler,只是创建出来,并没有发现有什么用,只是让ParameterMapping.Builder(),能正常通过。
        Configuration configuration = new Configuration();
        UnknownTypeHandler unknownTypeHandler = new UnknownTypeHandler(configuration);

        // 如果觉得不妥,还有一种方式,如若 parameterMappings集合中原来就有值,则取出其中一个来获取这两个对象,因为这两个对象configuration、unknownTypeHandler 在多个parameterMapping对象中都是用的一个。
        // Configuration configuration = (Configuration) ReflectUtil.getFieldValue(parameterMappings.get(0), "configuration");
        // UnknownTypeHandler unknownTypeHandler = (UnknownTypeHandler) ReflectUtil.getFieldValue(parameterMappings.get(0), "typeHandler");

        // ew.paramNameValuePairs.MPGENVAL1,固定写法,第一个映射路径就写:MPGENVAL1
        ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, "ew.paramNameValuePairs.MPGENVAL1", unknownTypeHandler);
        ParameterMapping parameterMapping = builder.build();
        // 添加绑定值 映射路径
        parameterMappings.add(parameterMapping);

        // 若有多个参数 则构建多个:绑定值映射路径。MPGENVAL1后面的数值需要加1
        // (类似JDBC中的 PreparedStatement,设置值,按顺序设置。)
         ParameterMapping.Builder builder2 = new ParameterMapping.Builder(configuration, "ew.paramNameValuePairs.MPGENVAL2", unknownTypeHandler);
         ParameterMapping parameterMapping2 = builder2.build();
        // 添加绑定值 映射路径
         parameterMappings.add(parameterMapping2);

         ParameterMapping.Builder builder3 = new ParameterMapping.Builder(configuration, "ew.paramNameValuePairs.MPGENVAL3", unknownTypeHandler);
         ParameterMapping parameterMapping3 = builder3.build();
        // 添加绑定值 映射路径
         parameterMappings.add(parameterMapping3);

        // 添加绑定值
        Map map = (Map) parameter;
        // 获取 QueryWrapper 对象
        QueryWrapper queryWrapper = (QueryWrapper) map.get("ew");

        // 通过反射获取 queryWrapper中 paramNameValuePairs的值
        // 这里使用一个工具(hutool)设置,如若直接使用反射获取QueryWrapper对象中的值会报错,找不到对应的字段
        // 因为paramNameValuePairs字段是QueryWrapper对象父级对象中的字段
        // 工具会循环向上查找
        HashMap hashMap = (HashMap)ReflectUtil.getFieldValue(queryWrapper, "paramNameValuePairs");
        // 将原有的绑定值清空
        hashMap.clear();

        // 添加新值,这里设置的key 要与 绑定值映射路径最后一级保持一致
        hashMap.put("MPGENVAL1", 18);// 第1个 “?”

        // 若需要多个参数 则添加多个值(类似JDBC中的 PreparedStatement,设置值,按顺序设置。)
        // 这里的key,要对应 绑定值映射路径的最后一级,例如:MPGENVAL2 对应 ew.paramNameValuePairs.MPGENVAL2
         hashMap.put("MPGENVAL2", "上海");// 第2个 “?”
         hashMap.put("MPGENVAL3", "北京");// 第3个 “?”

        // 最终执行的sql,例如:SELECT id,name,age,address FROM t_user WHERE age = 18 and (address = '上海' or address = '北京')

        // 设置 绑定参数数量,有几个参数写几。(实际上不加这一行也可以)
        // ReflectUtil.setFieldValue(queryWrapper, "paramNameSeq", new AtomicInteger(2));

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(targetSql);
    }

}

5.QueryInterceptor5(获取sql传入的参数数据)

package com.hechuan.interceptor;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.hechuan.doman.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.HashMap;
import java.util.Map;

/**
 * 查询拦截器5
 * 演示如何获取sql传入的参数
 */
public class QueryInterceptor5 implements InnerInterceptor {

    /**
     * 获取sql传入的参数
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user WHERE (name = ? AND age = ?)
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // parameter,实际上它就是个Map对象
        Map parameterMap = (Map) parameter;
        // QueryWrapper,实际上就是 在 代码查询的地方创建的QueryWrapper。
        Object obj = parameterMap.get("ew");
        QueryWrapper queryWrapper = (QueryWrapper) obj;

        // 获取 设置的 entity 对象
        // 在 代码查询的地方 向 QueryWrapper中,设置的 entity。
        // 例如:queryWrapper.setEntity(new User()) 那么此处的 对象就是 User对象
        Object entity = queryWrapper.getEntity();
        User user = (User) entity;
        Long id = user.getId();
        String name = user.getName();
        System.out.println("id:" + id);
        System.out.println("name:" + name);

        // 获取 通过 queryWrapper.eq()、queryWrapper.ne()、queryWrapper.in()...,等等方法设置的值。(反射获取)
        HashMap hashMap = (HashMap)ReflectUtil.getFieldValue(queryWrapper, "paramNameValuePairs");
        Object value1 = hashMap.get("MPGENVAL1");
        Object value2 = hashMap.get("MPGENVAL2");
        System.out.println("value1:" + value1);
        System.out.println("value2:" + value2);

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(originSql);
    }

}

6.QueryInterceptor6(优雅的拼接sql)

使用代码进行各种拼接。

package com.hechuan.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * 查询拦截器6
 */
public class QueryInterceptor6 implements InnerInterceptor {

    /**
     * 优雅的拼接sql
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 原始sql:SELECT  id,name,age,address  FROM t_user WHERE age = 18
        String originSql = boundSql.getSql();
        System.out.println("originSql:" + originSql);

        // 获取 Statement 执行sql的对象
        Statement statement = null;
        try {
            statement = CCJSqlParserUtil.parse(originSql);
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }

        Select select = (Select) statement;

        SelectBody selectBody = select.getSelectBody();
        if (selectBody == null) {
            return;
        }

        String targetSql = "";
        if (selectBody instanceof PlainSelect) {

            PlainSelect plainSelect = (PlainSelect) selectBody;
            Expression where = plainSelect.getWhere();

            // name = '张三'
            EqualsTo usesEqualsTo1 = new EqualsTo();
            usesEqualsTo1.setLeftExpression(new Column("name"));
            usesEqualsTo1.setRightExpression(new StringValue("张三"));

            // name = '赵六'
            EqualsTo usesEqualsTo2 = new EqualsTo();
            usesEqualsTo2.setLeftExpression(new Column("name"));
            usesEqualsTo2.setRightExpression(new StringValue("赵六"));

            // name = '张三' or name = '赵六'
            OrExpression orExpression = new OrExpression();
            orExpression.setLeftExpression(usesEqualsTo1);
            orExpression.setRightExpression(usesEqualsTo2);

            // (name = '张三' or name = '赵六')
            Parenthesis parenthesis = new Parenthesis();
            parenthesis.setExpression(orExpression);

            // ... AND (name = '张三' or name = '赵六')
            AndExpression andExpression = new AndExpression(where, parenthesis);
            // 将新的where条件sql 设置回去
            plainSelect.setWhere(andExpression);

            // 期望得到的sql:SELECT id, name, age, address FROM t_user WHERE age = ? AND (name = '张三' OR name = '赵六')
            targetSql = plainSelect.toString();
            System.out.println("targetSql:" + targetSql);
        }

        // 修改完成的sql 再设置回去
        PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
        mpBoundSql.sql(targetSql);
    }

}

最终:因为完全是自己盘的代码,目前符合项目需求。或许这么处理也会有不合理的地方,欢迎指正。
注意:转载请注明出处!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值