MybatisPlus自定义拦截器修改sql

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

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

在这里插入图片描述

二.创建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());
        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);
    }

}

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

  • 25
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
MybatisPlus是Mybatis的增强版本,它提供了更多的功能和便利的操作。自定义拦截器MybatisPlus中的一个重要特性,可以在SQL执行前后进行一些自定义的操作。 下面是一个示例,展示如何在MybatisPlus中自定义拦截器: 1. 创建一个自定义拦截器类,例如`MyQueryInterceptor`,并实现`Interceptor`接口。 ```java public class MyQueryInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在SQL执行前的操作 // ... // 执行原始的SQL语句 Object result = invocation.proceed(); // 在SQL执行后的操作 // ... return result; } } ``` 2. 在MybatisPlus的配置文件中配置自定义拦截器。 ```java @Configuration public class MybatisPlusConfig { @Bean public MyQueryInterceptor myQueryInterceptor() { return new MyQueryInterceptor(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(MyQueryInterceptor myQueryInterceptor) { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(myQueryInterceptor); return interceptor; } } ``` 3. 使用自定义拦截器。 ```java @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> getUserList(String keyword) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("username", keyword); return userMapper.selectList(queryWrapper); } } ``` 以上示例展示了如何在MybatisPlus中自定义拦截器,并在模糊查询时进行特殊字符的转义处理。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值