• jfinal中如何使用过滤器监控Druid监听SQL执行?


    摘要:最开始我想做的是通过拦截器拦截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. }

    我们发现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. import com.alibaba.druid.filter.FilterChain;
    3. import com.alibaba.druid.filter.FilterEventAdapter;
    4. import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
    5. import com.alibaba.druid.proxy.jdbc.StatementProxy;
    6. import com.jfinal.kit.LogKit;
    7. import java.sql.SQLException;
    8. public class DataScopeFilter extends FilterEventAdapter {
    9. @Override
    10. public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
    11. LogKit.info("statement_execute");
    12. return super.statement_execute(chain, statement, sql);
    13. }
    14. @Override
    15. protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
    16. LogKit.info("statementExecuteQueryBefore");
    17. super.statementExecuteQueryBefore(statement, sql);
    18. }
    19. @Override
    20. protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
    21. LogKit.info("statementExecuteQueryAfter");
    22. super.statementExecuteQueryAfter(statement, sql, resultSet);
    23. }
    24. @Override
    25. protected void statementExecuteBefore(StatementProxy statement, String sql) {
    26. LogKit.info("statementExecuteBefore");
    27. super.statementExecuteBefore(statement, sql);
    28. }
    29. @Override
    30. protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
    31. LogKit.info("statementExecuteAfter");
    32. super.statementExecuteAfter(statement, sql, result);
    33. }
    34. @Override
    35. public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)
    36. throws SQLException {
    37. LogKit.info("statement_executeQuery");
    38. return super.statement_executeQuery(chain, statement, sql);
    39. }
    40. }

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

    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. try {
    6. ResultSetProxy resultSet = super.statement_executeQuery(chain, statement, sql);
    7. if (resultSet != null) {
    8. statementExecuteQueryAfter(statement, sql, resultSet);
    9. resultSetOpenAfter(resultSet);
    10. }
    11. return resultSet;
    12. } catch (SQLException error) {
    13. statement_executeErrorAfter(statement, sql, error);
    14. throw error;
    15. } catch (RuntimeException error) {
    16. statement_executeErrorAfter(statement, sql, error);
    17. throw error;
    18. } catch (Error error) {
    19. statement_executeErrorAfter(statement, sql, error);
    20. throw error;
    21. }
    22. }

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

    因此我们,修改statement_executeQuery方法:

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

    如此,便让输出结果为:

    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. private final static Log LOG = LogFactory.getLog(DruidPooledPreparedStatement.class);
    3. private final PreparedStatementHolder holder;
    4. private final PreparedStatement stmt;
    5. private final String sql;
    6. ....
    7. }

    这也就以为着,该类一旦创建,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. statementPrepareAfter(statement);
    8. return statement;
    9. }

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

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

  • 相关阅读:
    为chrome浏览器单独设置代理服务器
    Reactor
    Win11 25188.1000补丁包介绍及下载地址
    一块RTX 3090加速训练YOLOv5s,时间减少11个小时,速度提升20%
    电力电子转战数字IC20220629day35——路科实验2b
    JAVA计算机毕业设计疫情展示平台Mybatis+源码+数据库+lw文档+系统+调试部署
    与HTTP相关的各种概念
    判断二叉树是否相等
    PNAS:人类头皮记录电位的时间尺度
    JAVA:实现文件中出现频率最高的K个单词以及出现的次数算法(附完整源码)
  • 原文地址:https://blog.csdn.net/devcloud/article/details/125527437