jfinal中如何使用過濾器監控Druid監聽SQL執行?

2022-06-29 21:01:00
摘要:最開始我想做的是通過攔截器攔截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修改邏輯,比如新增資料許可權等等。

 

點選關注,第一時間瞭解華為雲新鮮技術~