前言
工作中写的一段代码,备个份,以后兴许能直接用
功能描述:如果前端传入了排序规则,则优先按传入的字段进行排序,SQL原有的排序规则追加到末尾
注:我们项目里的分页查询,是基于XML的SQL执行的,没有直接使用mybatis-plus的 IPage
正文
定义拦截器
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.jetbrains.annotations.Nullable;
import org.springframework.util.ObjectUtils;
import java.sql.Connection;
import java.util.List;
/**
* 添加自定义排序规则
* 如果前端传入了排序规则,则优先按传入的字段进行排序,SQL原有的排序规则追加到末尾
* 这个SQL的处理方式【不是基于】字符串拼接,底层的SQL执行是基于 com.mysql.cj.jdbc.ClientPreparedStatement.execute,
* PreparedStatement 是防SQL注入的,强行做SQL注入会抛语法错误的异常 - syntax exception
* @author weiheng
* @date 2024-01-23
**/
@Slf4j
public class MybatisPageOrderSqlInterceptor extends JsqlParserSupport implements InnerInterceptor {
/** 排序方式 - 升序 */
private static final String SORT_ASC = "asc";
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
if (log.isDebugEnabled()) {
log.info(">>>>> 自定义排序拦截 StatementHandler[{}]", sh);
log.info(">>>>> 自定义排序拦截 connection[{}]", connection);
log.info(">>>>> 自定义排序拦截 transactionTimeout[{}]", transactionTimeout);
}
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
if (sct != SqlCommandType.SELECT) {
// 不是查询操作则直接跳过,不做任何操作
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
// 取分页查询的入参 PageDTO
PageDTO pageDto = getPageDTO(mpSh);
if (pageDto == null || CollUtil.isEmpty(pageDto.getOrderColumns())) {
// 不是自定义分页查询,不做处理
return;
}
mpBs.sql(parserMulti(mpBs.sql(), pageDto));
}
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
PageDTO dto;
if (obj instanceof PageDTO) {
dto = (PageDTO) obj;
} else {
// 如果不是分页查询,则不做处理
return;
}
// 1. 解析SQL
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
List<OrderByElement> originOrderList = plainSelect.getOrderByElements();
List<OrderByElement> clonedOriginOrder = null;
if (CollUtil.isNotEmpty(originOrderList)) {
// 创建副本,然后删除原有排序规则
clonedOriginOrder = BeanUtil.copyToList(originOrderList, OrderByElement.class);
originOrderList.clear();
}
// 2. 向 plainSelect 中添加自定义排序规则
addCustomizeSortColumns(dto, plainSelect);
// 3. 添加SQL的原始排序规则 - 追加到末尾
if (CollUtil.isNotEmpty(clonedOriginOrder)) {
plainSelect.addOrderByElements(clonedOriginOrder);
}
if (log.isDebugEnabled()) {
log.info("<<<<< 自定义排序拦截 plainSelect[{}]", plainSelect);
}
}
/**
* 添加自定义排序规则
*
* @param dto SQL查询入参
* @param plainSelect 明文
* @author weiheng
**/
private void addCustomizeSortColumns(PageDTO dto, PlainSelect plainSelect) {
// 获取主表别名
FromItem fromItem = plainSelect.getFromItem();
Alias alias = fromItem.getAlias();
String primaryTableAlias = null;
if (!ObjectUtils.isEmpty(alias)) {
primaryTableAlias = alias.getName();
}
// 组装排序
List<OrderColumnDTO> orderColumns = dto.getOrderColumns();
for (OrderColumnDTO c : orderColumns) {
// 构建新的排序规则
OrderByElement orderByElement = new OrderByElement();
// 设置排序 - 不传值则默认asc升序
orderByElement.setAsc(SORT_ASC.equalsIgnoreCase(c.getSort()));
// 设置排序字段 - 如果有连表查询,以主表字段为主进行排序(添加别名识别主表字段)
String columnName = c.getOrderColumn();
if (!ObjectUtils.isEmpty(alias)) {
columnName = primaryTableAlias + StrPool.DOT + columnName;
}
orderByElement.setExpression(new Column(columnName));
// 重新封装条件 - 优先按自定义进行排序
plainSelect.addOrderByElements(orderByElement);
}
}
/**
* 分页查询入参获取
* 大概搜了下,没有到3个入参的,如果有,请将 PageDTO 放到第1或第2的位置
* @param mpSh 处理对象
* @return 分页查询入参
* @author weiheng
**/
@Nullable
private PageDTO getPageDTO(PluginUtils.MPStatementHandler mpSh) {
PageDTO pageDto = null;
Object obj = mpSh.parameterHandler().getParameterObject();
try {
Object param = ((MapperMethod.ParamMap<?>) obj).get("param1");
if (param instanceof PageDTO) {
pageDto = (PageDTO) param;
} else {
param = ((MapperMethod.ParamMap<?>) obj).get("param2");
if (param instanceof PageDTO) {
pageDto = (PageDTO) param;
}
}
} catch (Exception e) {
// 没有从SQL中获取到对应的参数(没有param1或param2),不走自定义分页逻辑
}
return pageDto;
}
}
添加插件 - 添加自定义的拦截器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mybatis配置
* @author Heng.Wei
* @date 2022-05-11
**/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加防止全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 深度分页插件
interceptor.addInnerInterceptor(new MybatisHighPageInterceptor());
// 添加自定义排序
interceptor.addInnerInterceptor(new MybatisPageOrderSqlInterceptor());
return interceptor;
}
}
代码很简单,不多做废话了,自测OK
补充
从上图可以看到,这里会 for 循环调用所有拦截器的 beforePrepare 方法
然后再通过 invocation.proceed 反射调用 StatementHandler 接口的 prepare 方法
可以从图中看到接口实现是 PreparedStatementHandler 类的 instantiateStatement 方法
通过 PreparedStatementHandler 执行SQL操作
PS: 很意外昨晚发的帖子,基本纯代码,也没什么描述和备注,第二天早上看,展现量9、阅读199、新增粉丝2、收藏6、点赞3
感觉这个东西,喜欢看的同学还比较多,所以又稍微【补充】了一下内容