如何在PageHelper之前拦截sql

本文讲述了作者在项目中遇到的新需求——为区域权限添加拦截,通过MyBatis拦截器解决查询SQL动态添加权限,过程包括数据库调整、拦截器实现、加载顺序调整及注意事项。

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

        

 

        项目终于搞完一个版本了,最近几天可以一边慢慢修改bug,一边愉快的摸鱼了。哈哈哈。

        

         结果项目经理屁颠屁颠的跑过来,“王哥呀,客户那边新加了个需求,一点小小的改动,几分钟就完成了。”

        我:“我很忙的,还有很多bug需要改,等我这几天忙完了再说吧”。

        产品经理:“客户说了这个需求必须实现,不然不让上线,就加个简单的权限,让管理员只看到配置了的区域权限就好了”。

 

        我:“!!!!!!!这就是你说的小需求,差点可以让数据库重新设计一遍了”。

        没办法呀,需求必须得做呀。只能想下如何快速做完,然后愉快的去摸鱼。。。。

        

        首先我去数据库检查了下,涉及的各种记录查询表大约20来张表,每张表加个区域权限字段是必须的了。。。

        然后需要在处理查询的时候,根据登录用户添加上权限的过滤。难道我一个一个查询去加?那我不是不能愉快的摸鱼了。。

        既然都是在查询的sql加上一样的权限代码,那我想何不利用mybatis的拦截器拦截sql统一给处理了。

         立马百度一下,一顿操作,搞了个拦截器,简单的写了个测试,查询语句也给拼接上了。正想着轻松呀,终于没什么事,可以刷个抖音啥的了。哈哈哈。。。

        最后打开前端页面检查了下,全部报错了?????什么鬼,不是没问题吗?赶快看下日志,一看,在分页的情况下,sql拼接都是在分页后拼接的,导致拼接的后的sql报错了。

         这尼玛什么情况,我想要的是在分页之前对sql进行处理呀。。。赶快去看了下PageHelper的核心拦截器PageInterceptor,顺便去官网 瞅了一眼,发现了一个有用的东西。

         看了半天这个QueryInterceptor规范,虽然我也不知道我看懂没有,哈哈哈。

        这里面说的什么意思呢,就是说Executor的实现类CachingExecutor和BaseExecutor的以下方法都一样的,那我们可以直接在拦截器里面把这段代码拷贝进去,对于后面的拦截器就暴露6个参数的方法就行了。

        

 这个是pageHelper给出的示例(注意加了--------的注释)

@Intercepts(
    {
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class QueryInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //由于逻辑关系,只会进入一次
        if(args.length == 4){
            //4 个参数时
            //---------------这里就是拷贝的CachingExecutor或者BaseExecutor的代码
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        //TODO 自己要进行的各种处理
        //注:下面的方法可以根据自己的逻辑调用多次,在分页插件中,count 和 page 各调用了一次
        //-------------本来一般情况下是执行invocation.proceed(),继续执行拦截方法,但这里直接执行这个方法,相 
        //-------------当于替换了CachingExecutor或者BaseExecutor原来的实现。
        return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    }

         我看了下PageInterceptor的实现,也是按照这种原理实现的。所以我们也可以模仿着这种方法写个拦截器,这样就没问题。所以在上面代码TODO那里做了以下的代码逻辑开发,REGION_CODE 就是数据库的权限字段,然后拼接了sql。

 String sql = REGION_CODE + " in ('" +join +"')";
            String originalSql = boundSql.getSql();
            originalSql = "select * from (" + originalSql + ")   temp_data_scope where temp_data_scope." + sql;
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), originalSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, newBoundSql);

         赶快测试一看,发现还是有问题,PageInterceptor始终在我自己开发的拦截器之前执行。。。。查了下,是由于拦截器加载顺序的原因造成的。加载顺序1>2>3执行顺序就会变成3>2>1。

        为了让我们的拦截器先执行,我们需要在PageInterceptor拦截器后面添加我们的拦截器。关键项目是springboot项目,默认会先加载自己项目的拦截器。

        尝试了很多方法,包括@Order注解(添加拦截器没实现利用order排序)

        @AutoConfigureBefore(这个是控制自动注入的配置类的加载顺序的)

        ---------------------------------------------------------------------------------------------------------

        既然没有方法了,那我只有搞个骚的来解决了,哈哈哈。在我添加自己的拦截器前注入PageHelper的配置类,那么肯定先于我的拦截器添加。

@Configuration
public class AddMybatisInterceptorConfiguration {

    @Resource
    private List<SqlSessionFactory> sqlSessionFactoryList;
    @Resource
    private PageHelperAutoConfiguration pageHelperAutoConfiguration;


    @PostConstruct
    public void addPageInterceptor() {
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            //这个调用没有什么实际意义,只是为了检查PageHelperAutoConfiguration确实注入了
            pageHelperAutoConfiguration.pageHelperProperties();
            //这个QueryInterceptor就是我实现的添加拦截代码的实现类
            sqlSessionFactory.getConfiguration().addInterceptor(new QueryInterceptor());
        }
    }

}

         这样一搞,然后测试,果然没有问题了。

        

        注意点:

                1.拦截器的type是Executor的,别用成了StatementHandler、PameterHandler和ResultSetHandler。(用成其他的无论如何配置都会在分页后执行)

                2.记得添加拦截器之前注入PageHelper的配置类。

 

### GIoU 的计算公式 GIoU (Generalized Intersection over Union) 是一种扩展的 IoU 损失函数,解决了传统 IoU 在边界框无重叠情况下的不足。其核心思想是在 IoU 基础上引入了一个额外项,用于衡量预测框和真实框之间的相对位置关系。 #### 定义与公式 设 \( B \) 和 \( B^{gt} \) 分别表示预测框和真实框,\( C \) 表示能够覆盖 \( B \) 和 \( B^{gt} \) 的最小闭合区域(即包围盒)。则: \[ \text{IoU}(B, B^{gt}) = \frac{\text{Area}(B \cap B^{gt})}{\text{Area}(B \cup B^{gt})} \] 而 GIoU 的定义为: \[ \text{GIoU}(B, B^{gt}) = \text{IoU}(B, B^{gt}) - \frac{\text{Area}(C \setminus (B \cup B^{gt}))}{\text{Area}(C)} \] 其中: - \( \text{Area}(B \cap B^{gt}) \) 表示预测框和真实框交集面积。 - \( \text{Area}(B \cup B^{gt}) \) 表示预测框和真实框并集面积。 - \( \text{Area}(C) \) 表示最小闭包矩形 \( C \) 的总面积。 - \( \text{Area}(C \setminus (B \cup B^{gt})) \) 表示最小闭包矩形减去预测框和真实框并集部分的剩余面积。 通过这一公式,即使预测框和真实框完全不相交,也可以得到一个有意义的损失值[^1]。 --- ### 通用 IoU 损失函数定义 传统的 IoU 损失函数可以被定义为: \[ L_{\text{IoU}}(B, B^{gt}) = 1 - \text{IoU}(B, B^{gt}) \] 然而,这种形式在预测框和真实框完全没有重叠的情况下会失效,因为此时 \( \text{IoU}(B, B^{gt}) = 0 \),导致梯度消失问题。 相比之下,GIoU 提供了一种更鲁棒的方式处理这种情况,使得即使没有重叠也能提供有效的梯度方向[^2]。 --- ### Python 实现代码 以下是基于上述公式的 GIoU 计算实现: ```python import torch def giou_loss(pred_boxes, target_boxes): """ Calculate the Generalized IoU loss between predicted and target bounding boxes. Args: pred_boxes: Tensor of shape (N, 4), where N is the number of boxes, each box represented as [x1, y1, x2, y2]. target_boxes: Tensor of shape (N, 4). Returns: A scalar tensor representing the mean GIoU loss across all boxes. """ # Compute intersection coordinates xA = torch.max(pred_boxes[:, 0], target_boxes[:, 0]) yA = torch.max(pred_boxes[:, 1], target_boxes[:, 1]) xB = torch.min(pred_boxes[:, 2], target_boxes[:, 2]) yB = torch.min(pred_boxes[:, 3], target_boxes[:, 3]) # Compute area of intersection rectangle I inter_area = torch.clamp(xB - xA + 1, min=0.0) * torch.clamp(yB - yA + 1, min=0.0) # Compute areas of prediction and ground truth rectangles pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0] + 1) * \ (pred_boxes[:, 3] - pred_boxes[:, 1] + 1) target_area = (target_boxes[:, 2] - target_boxes[:, 0] + 1) * \ (target_boxes[:, 3] - target_boxes[:, 1] + 1) # Compute union area U union_area = pred_area + target_area - inter_area # Compute IOU iou = inter_area / union_area # Compute smallest enclosing box C c_xA = torch.min(pred_boxes[:, 0], target_boxes[:, 0]) c_yA = torch.min(pred_boxes[:, 1], target_boxes[:, 1]) c_xB = torch.max(pred_boxes[:, 2], target_boxes[:, 2]) c_yB = torch.max(pred_boxes[:, 3], target_boxes[:, 3]) # Area of enclosing box C enclose_area = (c_xB - c_xA + 1) * (c_yB - c_yA + 1) # Compute GIoU giou = iou - ((enclose_area - union_area) / enclose_area) # Return GIoU loss return 1 - giou.mean() ``` 此代码实现了 GIoU 损失的计算过程,并支持批量输入的张量操作。 --- ### 总结 GIoU 改进了传统 IoU 损失函数存在的缺陷,特别是在预测框和真实框无重叠的情况下的表现更为优越。它不仅保留了 IoU 对于重叠区域的有效评估能力,还通过引入外部闭包区域进一步增强了模型的学习效果。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值