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

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

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

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

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

配置过滤器

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

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

public void configPlugin(Plugins me) {     DruidPlugin druidPlugin = createDruidPlugin_holdoa();     druidPlugin.setPublicKey(p.get("publicKeydebug").trim());     wallFilter = new WallFilter();     wallFilter.setDbType("mysql");     druidPlugin_oa.addFilter(wallFilter);     druidPlugin_oa.addFilter(new StatFilter());     me.add(druidPlugin); }

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

import com.alibaba.druid.filter.FilterEventAdapter; public class DataScopeFilter extends FilterEventAdapter {  }

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

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

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

package xxxx.xxxx;  import com.alibaba.druid.filter.FilterChain; import com.alibaba.druid.filter.FilterEventAdapter; import com.alibaba.druid.proxy.jdbc.ResultSetProxy; import com.alibaba.druid.proxy.jdbc.StatementProxy; import com.jfinal.kit.LogKit; import java.sql.SQLException;  public class DataScopeFilter extends FilterEventAdapter {      @Override     public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {         LogKit.info("statement_execute");         return super.statement_execute(chain, statement, sql);     }      @Override     protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {         LogKit.info("statementExecuteQueryBefore");         super.statementExecuteQueryBefore(statement, sql);     }      @Override     protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {         LogKit.info("statementExecuteQueryAfter");         super.statementExecuteQueryAfter(statement, sql, resultSet);     }      @Override     protected void statementExecuteBefore(StatementProxy statement, String sql) {         LogKit.info("statementExecuteBefore");         super.statementExecuteBefore(statement, sql);     }      @Override     protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {         LogKit.info("statementExecuteAfter");         super.statementExecuteAfter(statement, sql, result);     }      @Override     public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)             throws SQLException {         LogKit.info("statement_executeQuery");         return super.statement_executeQuery(chain, statement, sql);     } }

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

druidPlugin.addFilter(new DataScopeFilter());

发起其执行顺序为:

statement_executeQuery statementExecuteQueryBefore statementExecuteQueryAfter

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

@Override     public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)                                                                                                          throws SQLException {         statementExecuteQueryBefore(statement, sql);          try {             ResultSetProxy resultSet = super.statement_executeQuery(chain, statement, sql);              if (resultSet != null) {                 statementExecuteQueryAfter(statement, sql, resultSet);                 resultSetOpenAfter(resultSet);             }              return resultSet;         } catch (SQLException error) {             statement_executeErrorAfter(statement, sql, error);             throw error;         } catch (RuntimeException error) {             statement_executeErrorAfter(statement, sql, error);             throw error;         } catch (Error error) {             statement_executeErrorAfter(statement, sql, error);             throw error;         }     }

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

因此我们,修改statement_executeQuery方法:

 @Override     public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql)             throws SQLException {          statementExecuteQueryBefore(statement, sql);         ResultSetProxy result = chain.statement_executeQuery(statement, sql);         statementExecuteQueryAfter(statement, sql, result);         return result;     }

如此,便让输出结果为:

statementExecuteQueryBefore statement_executeQuery statementExecuteQueryAfter

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

sql被声明为final类型

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

public class DruidPooledPreparedStatement extends DruidPooledStatement implements PreparedStatement {      private final static Log              LOG = LogFactory.getLog(DruidPooledPreparedStatement.class);      private final PreparedStatementHolder holder;     private final PreparedStatement       stmt;     private final String                  sql;      .... }

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

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

@Override     public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql)             throws SQLException {         // 可以达到修改SQL的目的         sql += " LIMIT 1";         PreparedStatementProxy statement = super.connection_prepareStatement(chain, connection, sql);          statementPrepareAfter(statement);          return statement;     }

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

 

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

发表评论

相关文章