摘要:最开始我想做的是通过拦截器拦截SQL执行,但是经过测试发现,过滤器至少可以监听每一个SQL的执行与返回结果。因此,将这一次探索过程记录下来。

本文分享自华为云社区《jfinal中使用过滤器监控Druid的SQL执行【五月07】》,作者:KevinQ 。

最开始我想做的是通过拦截器拦截SQL执行,比如类似与PageHelper这种插件,通过拦截器或过滤器,手动修改SQL语句,以实现某些业务需求,比如执行分页,或者限制访问的数据权限等等。但是查到资料说过滤器不是干这个的,干这个的是数据库中间件干的事情,比如MyCat等。

但是经过测试发现,过滤器至少可以监听每一个SQL的执行与返回结果。因此,将这一次探索过程记录下来。

配置过滤器

在jfinal的启动配置类中,有一个函数configPlugin(Plugins me)函数来配置插件,这个函数会在jfinal启动时调用,这个函数的参数是Plugins me,这个参数是一个插件管理器,可以通过这个插件管理器来添加插件。

数据库插件Druid就是在该函数内添加的。

  1. public void configPlugin(Plugins me) {
  2. DruidPlugin druidPlugin = createDruidPlugin_holdoa();
  3. druidPlugin.setPublicKey(p.get("publicKeydebug").trim());
  4. wallFilter = new WallFilter();
  5. wallFilter.setDbType("mysql");
  6. druidPlugin_oa.addFilter(wallFilter);
  7. druidPlugin_oa.addFilter(new StatFilter());
  8. me.add(druidPlugin);
  9. }

我们参考WallFilter以及StatFilter也创建一个过滤器类:

  1. import com.alibaba.druid.filter.FilterEventAdapter;
  2. public class DataScopeFilter extends FilterEventAdapter {
  3.  
  4. }

我们发现FilterEventAdapter中的方法大概有这几个:

  1. public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {...}
  2. protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {...}
  3. protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {...}
  4. protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {...}
  5. protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {...}
  6. protected void statementExecuteBefore(StatementProxy statement, String sql) {...}
  7. protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {...}

我们复写这几个方法来看一下(排除Update方法,因为我们更关心查询语句)

  1. package xxxx.xxxx;
  2.  
  3. import com.alibaba.druid.filter.FilterChain;
  4. import com.alibaba.druid.filter.FilterEventAdapter;
  5. import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
  6. import com.alibaba.druid.proxy.jdbc.StatementProxy;
  7. import com.jfinal.kit.LogKit;
  8. import java.sql.SQLException;
  9.  
  10. public class DataScopeFilter extends FilterEventAdapter {
  11.  
  12. @Override
  13. public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
  14. LogKit.info("statement_execute");
  15. return super.statement_execute(chain, statement, sql);
  16. }
  17.  
  18. @Override
  19. protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
  20. LogKit.info("statementExecuteQueryBefore");
  21. super.statementExecuteQueryBefore(statement, sql);
  22. }
  23.  
  24. @Override
  25. protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
  26. LogKit.info("statementExecuteQueryAfter");
  27. super.statementExecuteQueryAfter(statement, sql, resultSet);
  28. }
  29.  
  30. @Override
  31. protected void statementExecuteBefore(StatementProxy statement, String sql) {
  32. LogKit.info("statementExecuteBefore");
  33. super.statementExecuteBefore(statement, sql);
  34. }
  35.  
  36. @Override
  37. protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
  38. LogKit.info("statementExecuteAfter");
  39. super.statementExecuteAfter(statement, sql, result);
  40. }
  41.  
  42. @Override
  43. public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)
  44. throws SQLException {
  45. LogKit.info("statement_executeQuery");
  46. return super.statement_executeQuery(chain, statement, sql);
  47. }
  48. }

然后再config配置类中添加过滤器:

  1. druidPlugin.addFilter(new DataScopeFilter());

发起其执行顺序为:

  1. statement_executeQuery
  2. statementExecuteQueryBefore
  3. statementExecuteQueryAfter

查看父级代码,发现其执行逻辑是,首先执行statement_executeQuery,然后因为调用父级的方法,而父级方法体为:

  1. @Override
  2. public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)
  3. throws SQLException {
  4. statementExecuteQueryBefore(statement, sql);
  5.  
  6. try {
  7. ResultSetProxy resultSet = super.statement_executeQuery(chain, statement, sql);
  8.  
  9. if (resultSet != null) {
  10. statementExecuteQueryAfter(statement, sql, resultSet);
  11. resultSetOpenAfter(resultSet);
  12. }
  13.  
  14. return resultSet;
  15. } catch (SQLException error) {
  16. statement_executeErrorAfter(statement, sql, error);
  17. throw error;
  18. } catch (RuntimeException error) {
  19. statement_executeErrorAfter(statement, sql, error);
  20. throw error;
  21. } catch (Error error) {
  22. statement_executeErrorAfter(statement, sql, error);
  23. throw error;
  24. }
  25. }

从而进一步触发statementExecuteQueryBefore方法与statementExecuteQueryAfter方法。

因此我们,修改statement_executeQuery方法:

  1. @Override
  2. public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)
  3. throws SQLException {
  4.  
  5. statementExecuteQueryBefore(statement, sql);
  6. ResultSetProxy result = chain.statement_executeQuery(statement, sql);
  7. statementExecuteQueryAfter(statement, sql, result);
  8. return result;
  9. }

如此,便让输出结果为:

  1. statementExecuteQueryBefore
  2. statement_executeQuery
  3. statementExecuteQueryAfter

我们可以在Before或者After方法中添加一些逻辑,比如:记录SQL的实际执行人,操作时间,请求执行SQL的接口。

sql被声明为final类型

发现执行的SQL在Druid中对应的类是:DruidPooledPreparedStatement,其类结构为:

  1. public class DruidPooledPreparedStatement extends DruidPooledStatement implements PreparedStatement {
  2.  
  3. private final static Log LOG = LogFactory.getLog(DruidPooledPreparedStatement.class);
  4.  
  5. private final PreparedStatementHolder holder;
  6. private final PreparedStatement stmt;
  7. private final String sql;
  8.  
  9. ....
  10. }

这也就以为着,该类一旦创建,SQL设置后就不允许再修改了,因此,我们需要修改SQL的话,就需要在prepared对象生成之前就修改到对应的执行SQL。

在调试过程中,发现需要覆盖下面这个方法:

  1. @Override
  2. public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql)
  3. throws SQLException {
  4. // 可以达到修改SQL的目的
  5. sql += " LIMIT 1";
  6. PreparedStatementProxy statement = super.connection_prepareStatement(chain, connection, sql);
  7.  
  8. statementPrepareAfter(statement);
  9.  
  10. return statement;
  11. }

我们可以在这里添加自定义的SQL修改逻辑,比如添加数据权限等等。

点击关注,第一时间了解华为云新鲜技术~

jfinal中如何使用过滤器监控Druid监听SQL执行?的更多相关文章

  1. phalcon: Profiling分析 profilter / Plugin结合,dispatcher调度控制器 监听sql执行日志

    个人觉得profilter 跟 logger 功能差不多,logger的功能在于写入,profilter功能在于sql后及时显示分析.都是对sql执行的的分析:一个是写入log文件,一个是直接在页面展 ...

  2. Blazor和Vue对比学习(基础1.8):Blazor中实现计算属性和数据监听

    1.7章<传递UI片断>,需要做几个案例,这部分暂停消化几天.我们先把基础部分相对简单的最后两章学习了. 计算属性和数据监听是Vue当中的概念,本质上都是监听数据的变化,然后做出响应.两者 ...

  3. 友盟分享到微信 监听不执行 监听只执行onStart,(onResult,onError,onCancel 不执行)

    最近在做一个项目 有一个需求是要分享项目中的一个商品 这对于我来说简直是 so easy (项目是三个人一起写的) 正好看到之前有同事写完了  我就拿过来用吧  一顿复制粘贴  大功告成   这个是监 ...

  4. ThinkPHP 数据库操作(六) : 查询事件、事务操作、监听SQL

    查询事件 查询事件(V5.0.4+) 从 5.0.4+ 版本开始,增加了数据库的CURD操作事件支持,包括: 查询事件仅支持 find . select . insert . update 和 del ...

  5. quartz2.3.0(九)job任务监听器,监听任务执行前、后、取消手动处理方法

    job1任务类 package org.quartz.examples.example9; import java.util.Date; import org.quartz.Job; import o ...

  6. 监听EF执行的sql语句及状态

    1.监听EF执行sql的方式 db.Database.Log += c => Console.WriteLine($"sql:{c}"); SQL Server Profil ...

  7. angularjs中 $watch 和$on 2种监听的区别?

    1.$watch简单使用 $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. $watch(watchExpression, listener, objectEq ...

  8. Vue ---- 表单指令 条件指令 循环指令 分隔符 过滤器 计算属性 监听属性

    目录 案例讲解: 一. 表单指令 1.重点: 补充 2.单选框 3.单一复选框 4.多复选框 二 . 条件指令 v-if/v-show ... v-clock 三 . 循环指令 string arra ...

  9. JS 中的事件绑定、事件监听、事件委托

    事件绑定 要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数.所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称. 在JavaScript中,有 ...

随机推荐

  1. PowerBI开发:用自然语言来探索数据--Q&A

    Power BI报表的用户,肯定会被Q&A的功能惊艳到,在查看报表时,仅仅通过输入文本就可以探索数据,并且结果是可视化的,更令人惊艳的时,结果几乎是实时显示出来的.这使得Q&A Vis ...

  2. partOne讲解思路

    讲解思路   分解:把一个复杂的大问题,拆解成更可执行.更好理解的小步骤. 模式识别:找出相似模式,高效解决细分问题. 抽象:聚焦最重要的信息,忽视无用细节. 算法:设计一步一步的解决路径,解决整个问 ...

  3. IDEA Debug过程中使用Drop Frame或Reset Frame实现操作回退

    大家在Debug程序的时候,是否遇到过因为"下一步"按太快,而导致跳过了想要深入分析的那段代码?是不是很想要有"回到上一步"这样的操作呢? 在IDEA中就提供了 ...

  4. 听说Integer有bug?1000不等于1000?

    bug? 前几天有位朋友找我,说:"老哥,老哥,我好像发现了Integer一个bug,你帮我看看什么情况?",说完给了我两个很简单的demo,上代码. 100 == 100 100 ...

  5. 基于全志A40i开发板——Linux-RT内核应用开发教程(1)

    目录 1 Linux-RT内核简介 3 2 Linux系统实时性测试 3 3 rt_gpio_ctrl案例 10 4 rt_input案例 15 本文为Linux-RT内核应用开发教程的第一章节--L ...

  6. JuiceFS 缓存预热详解

    缓存预热是一个比较常见的概念,相信很多小伙伴都有所了解.对于 JuiceFS 来说,缓存预热就是将需要操作的数据预先从对象存储拉取到本地,从而获得与使用本地存储类似的性能表现. 缓存预热 JuiceF ...

  7. Python学习笔记: 装饰器Decorator

    介绍 装饰器是对功能函数的加强. 在原来的功能函数之外,另外定义一个装饰器函数,对原来的功能函数进行封装(wrapper)并在wrapper的过程中增加一些辅助功能. 应用场景 如下场景: 业务函数f ...

  8. 数据结构篇(2) ts实现单链表

    interface NodeItem { prev: NodeItem | null next: NodeItem | null data: any } class NodeItem { prev: ...

  9. vsphere部署OVF虚拟机提示未能部署OVF软件包

    一.从vshere平台导出OVF,准备导入到另一个vsphere平台提示:传输入失败:Error transferring file to https://172.22.1.85/nfc/5267db ...

  10. golang md5加密和python md5加密比较

    python md5加密和golang md5加密各有不同,记录于此做备忘 Python 方法 md5 import base64 import hashlib def get_md5_data(bo ...