使用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);
}
}
最终:因为完全是自己盘的代码,目前符合项目需求。或许这么处理也会有不合理的地方,欢迎指正。
注意:转载请注明出处!