jdbc执行监听器
2023/11/12大约 8 分钟
该监听器用来监听jdbc的执行前后sql和参数还有异常和耗时,可以用来记录慢sql。
JdbcExecutorListener
| 方法 | 默认 | 描述 | 
|---|---|---|
| enable | false | 表示是否使用监听器 | 
| onExecuteBefore | 空 | 用来记录监听前的参数信息 | 
| onExecuteAfter | 空 | 用来监听监听后的参数信息 | 
JdbcExecuteBeforeArg
执行前参数
| 方法 | 类型 | 默认 | 描述 | 
|---|---|---|---|
| traceId | String | UUID.randomUUID().toString() | 用来连接前后执行的追踪id,可以自己重写 | 
| sql | String | 执行的sql | 用来记录监听前的参数信息 | 
| sqlParameters | List<List<SQLParameter>> | Collections.emptyList() | 用来监听监听后的参数信息 | 
| start | long | System.currentTimeMillis() | 执行当前方法的毫秒数 | 
| state | Map<String,Object> | null | 用来监听监听后的参数信息 | 
JdbcExecuteAfterArg
执行后参数
| 方法 | 类型 | 默认 | 描述 | 
|---|---|---|---|
| beforeArg | JdbcExecuteBeforeArg | null | 执行前的参数 | 
| rows | int | 受影响行数 | 如果当前是查询那么这个值永远为0,因为查询是stream模式获取,无法在jdbc返回后知晓结果 | 
| sqlParameters | List<List<SQLParameter>> | Collections.emptyList() | 用来监听监听后的参数信息 | 
| exception | Exception | null | 执行时发生的异常 | 
| end | long | System.currentTimeMillis() | 执行当前方法的毫秒数 | 
自定义监听器
创建一个自定义监听器监听耗时3秒以上的sql,并且发送到监控平台
public class LogSlowSQLListener implements JdbcExecutorListener {
    @Override
    public boolean enable() {
        return true;//表示需要开启监听
    }
    @Override
    public void onExecuteBefore(JdbcExecuteBeforeArg arg) {
        //这边可以通过setState来传递参数
//        HashMap<String, Object> state = new HashMap<>();
//        arg.setState(state);
    }
    @Override
    public void onExecuteAfter(JdbcExecuteAfterArg afterArg) {
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        JdbcExecuteBeforeArg beforeArg = afterArg.getBeforeArg();
        //通过getState来获取before的参数
//        Map<String, Object> state = beforeArg.getState();
        //记录耗时操作
        long elapsed = afterArg.getEnd() - beforeArg.getStart();
        //耗时3秒以上的sql需要记录
        //因为sqlParameters第一层大于1表示是批处理,批处理的时间一般是比较多的你可以选择
        //不记录本次sql或者只记录sql不记录sql参数自行处理
        if(elapsed>=3*1000 && beforeArg.getSqlParameters().size()<=1){
            //发送http请求
            
            String sql = beforeArg.getSql();
            List<List<SQLParameter>> sqlParameters = beforeArg.getSqlParameters();
            if(sqlParameters.size()==1){
                String params =  EasySQLUtil.sqlParameterToString(sqlParameters.get(0))
            }
            Exception exception = afterArg.getException();
        }
    }
}替换掉系统的监听器
easyQueryClient = EasyQueryBootstrapper.defaultBuilderConfiguration()
                .setDefaultDataSource(dataSource)
                .optionConfigure(op -> {
                    op.setDeleteThrowError(false);
                })
                .useDatabaseConfigure(new MySQLDatabaseConfiguration())
                //替换掉系统的默认监听行为
                .replaceService(JdbcExecutorListener.class, LogSlowSQLListener.class)
                .build();当然这种情况只适合无需外部参数传入的情况下,如果我们需要传递外部bean到框架内部可以直接注册
循环监听
很多时候我们可能会将监听到的超时sql进行mq的发送然后来存入到自己的数据库,但是因为存入数据库也会触发jdbcListener所以eq贴心的给用户添加了忽略jdbcListener的功能,保证插入到数据库不会出现循环监听
//查询数据移除JDBC_LISTEN行为
List<Topic> list = easyEntityQuery.queryable(Topic.class)
        .configure(config -> {
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        }).toList();
//插入数据移除JDBC_LISTEN行为
Topic topic = new Topic();
easyEntityQuery.insertable(topic)
        .configure(config->{
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        }).executeRows();
//更新数据移除JDBC_LISTEN行为
Topic topic = new Topic();
easyEntityQuery.updatable(topic)
        .configure(config->{
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        }).executeRows();
easyEntityQuery.updatable(Topic.class)
        .configure(config->{
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        })
        .setColumns(t -> {
            t.title().set("123");
        })
        .where(t -> {
            t.id().eq("123");
        })
        .executeRows();
//删除数据移除JDBC_LISTEN行为
Topic topic = new Topic();
easyEntityQuery.deletable(topic)
        .configure(config->{
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        }).executeRows();
easyEntityQuery.deletable(Topic.class)
        .configure(config->{
            config.getBehavior().removeBehavior(EasyBehaviorEnum.JDBC_LISTEN);
        })
        .where(t -> {
            t.id().eq("123");
        })
        .executeRows();springboot为例
假设我们有这个一个日志请求bean通过@Component注册到了springboot中
@Component
public class HttpLogRequest {
    public void send(Object request){
        
    }
}
public class LogSlowSQLListener implements JdbcExecutorListener {
    //通过构造函数注入
    private final HttpLogRequest httpLogRequest;
    public LogSlowSQLListener(HttpLogRequest httpLogRequest){
        this.httpLogRequest = httpLogRequest;
    }
    @Override
    public boolean enable() {
        return true;//表示需要开启监听
    }
    @Override
    public void onExecuteBefore(JdbcExecuteBeforeArg arg) {
        //这边可以通过setState来传递参数
//        HashMap<String, Object> state = new HashMap<>();
//        arg.setState(state);
    }
    @Override
    public void onExecuteAfter(JdbcExecuteAfterArg afterArg) {
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        JdbcExecuteBeforeArg beforeArg = afterArg.getBeforeArg();
        //通过getState来获取before的参数
//        Map<String, Object> state = beforeArg.getState();
        //记录耗时操作
        long elapsed = afterArg.getEnd() - beforeArg.getStart();
        //耗时3秒以上的sql需要记录
        //因为sqlParameters第一层大于1表示是批处理,批处理的时间一般是比较多的你可以选择
        //不记录本次sql或者只记录sql不记录sql参数自行处理
        if(elapsed>=3*1000 && beforeArg.getSqlParameters().size()<=1){
            //发送http请求
            String sql = beforeArg.getSql();
            List<List<SQLParameter>> sqlParameters = beforeArg.getSqlParameters();
            if(sqlParameters.size()==1){
                String params =  EasySQLUtil.sqlParameterToString(sqlParameters.get(0))
            }
            Exception exception = afterArg.getException();
        }
    }
}
@Component
public class MyStarterConfigurer implements StarterConfigurer {
    @Autowired
    private HttpLogRequest httpLogRequest;
    @Override
    public void configure(ServiceCollection services) {
        services.addService(httpLogRequest);//直接注册实例到easy-query内部的依赖注入容器里面
        services.addService(JdbcExecutorListener.class, LogSlowSQLListener.class);
    }
}只要将实例对象HttpLogRequest直接注入到容器中,那么easy-query内部的所有服务都可以直接获取到HttpLogRequest,所以LogSlowSQLListener也可以获取到
solon
@Component
public class HttpLogRequest {
    public void send(Object request){
    }
}
public class LogSlowSQLListener implements JdbcExecutorListener {
    private final HttpLogRequest httpLogRequest;
    public LogSlowSQLListener(HttpLogRequest httpLogRequest){
        this.httpLogRequest = httpLogRequest;
    }
    @Override
    public boolean enable() {
        return true;//表示需要开启监听
    }
    @Override
    public void onExecuteBefore(JdbcExecuteBeforeArg arg) {
        //这边可以通过setState来传递参数
//        HashMap<String, Object> state = new HashMap<>();
//        arg.setState(state);
    }
    @Override
    public void onExecuteAfter(JdbcExecuteAfterArg afterArg) {
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        JdbcExecuteBeforeArg beforeArg = afterArg.getBeforeArg();
        //通过getState来获取before的参数
//        Map<String, Object> state = beforeArg.getState();
        //记录耗时操作
        long elapsed = afterArg.getEnd() - beforeArg.getStart();
        //耗时3秒以上的sql需要记录
        //因为sqlParameters第一层大于1表示是批处理,批处理的时间一般是比较多的你可以选择
        //不记录本次sql或者只记录sql不记录sql参数自行处理
        if(elapsed>=3*1000 && beforeArg.getSqlParameters().size()<=1){
            //发送http请求
            String sql = beforeArg.getSql();
            List<List<SQLParameter>> sqlParameters = beforeArg.getSqlParameters();
            if(sqlParameters.size()==1){
                String params =  EasySQLUtil.sqlParameterToString(sqlParameters.get(0))
            }
            Exception exception = afterArg.getException();
        }
    }
}solon也是一样将需要的bean注入进来,不同的是solon通过监听订阅事件注入即可
public class App {
    public static void main(String[] args) {
        Solon.start(App.class,args,app->{
            app.onEvent(EasyQueryBuilderConfiguration.class,e->{
                HttpLogRequest httpLogRequest = app.context().getBean(HttpLogRequest.class);
                e.replaceService(httpLogRequest);
                e.replaceService(JdbcExecutorListener.class,LogSlowSQLListener.class);
            });
        });
    }
}说明!!!
如果solon在app处onEvent无法获取bean那么可以通过延迟获取的方式
public class LogSlowSQLListener implements JdbcExecutorListener {
    private final ServiceProvider serviceProvider;
    public LogSlowSQLListener(ServiceProvider serviceProvider){
        this.serviceProvider = serviceProvider;
    }
    @Override
    public boolean enable() {
        return true;//表示需要开启监听
    }
    @Override
    public void onExecuteBefore(JdbcExecuteBeforeArg arg) {
        //这边可以通过setState来传递参数
//        HashMap<String, Object> state = new HashMap<>();
//        arg.setState(state);
    }
    @Override
    public void onExecuteAfter(JdbcExecuteAfterArg afterArg) {
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        //为了不影响性能建议采用异步线程池发送http,同步的话会影响性能
        JdbcExecuteBeforeArg beforeArg = afterArg.getBeforeArg();
        //通过getState来获取before的参数
//        Map<String, Object> state = beforeArg.getState();
        //记录耗时操作
        long elapsed = afterArg.getEnd() - beforeArg.getStart();
        //耗时3秒以上的sql需要记录
        //因为sqlParameters第一层大于1表示是批处理,批处理的时间一般是比较多的你可以选择
        //不记录本次sql或者只记录sql不记录sql参数自行处理
        if(elapsed>=3*1000 && beforeArg.getSqlParameters().size()<=1){
            //通过serviceProvider来获取注册的bean实例
            AppContext service = serviceProvider.getService(AppContext.class);
            HttpLogRequest httpLogRequest = service.getBean(HttpLogRequest.class);
            //发送http请求
            String sql = beforeArg.getSql();
            List<List<SQLParameter>> sqlParameters = beforeArg.getSqlParameters();
            if(sqlParameters.size()==1){
                String params =  EasySQLUtil.sqlParameterToString(sqlParameters.get(0))
            }
            Exception exception = afterArg.getException();
        }
    }
}
public class App {
    public static void main(String[] args) {
        Solon.start(App.class,args,app->{
            app.onEvent(EasyQueryBuilderConfiguration.class,e->{
//                HttpLogRequest httpLogRequest = app.context().getBean(HttpLogRequest.class);
                e.replaceService(app.context());
                e.replaceService(JdbcExecutorListener.class,LogSlowSQLListener.class);
            });
        });
    }
}实战LogSlowSQLListener
public class LogSlowSQLListener implements JdbcExecutorListener {
    private static final Logger log = LoggerFactory.getLogger(LogSlowSQLListener.class);
    private final long slowMillis;
    public LogSlowSQLListener(long slowMillis){
        this.slowMillis = slowMillis;
    }
    @Override
    public boolean enable() {
        return true;
    }
    @Override
    public void onExecuteBefore(JdbcExecuteBeforeArg arg) {
    }
    @Override
    public void onExecuteAfter(JdbcExecuteAfterArg afterArg) {
        JdbcExecuteBeforeArg beforeArg = afterArg.getBeforeArg();
        //通过getState来获取before的参数
//        Map<String, Object> state = beforeArg.getState();
        //记录耗时操作
        long elapsed = afterArg.getEnd() - beforeArg.getStart();
        //耗时3秒以上的sql需要记录
        //因为sqlParameters第一层大于1表示是批处理,批处理的时间一般是比较多的你可以选择
        //不记录本次sql或者只记录sql不记录sql参数自行处理
        if (elapsed >= slowMillis && beforeArg.getSqlParameters().size() <= 5) {
            //发送http请求
            String sql = beforeArg.getSql();
            List<List<SQLParameter>> sqlParameters = beforeArg.getSqlParameters();
            String sqlParameter = getSQLParameter(sqlParameters);
            Exception exception = afterArg.getException();
            if (exception == null) {
                log.error("!!!!!!execute sql slow,elapsed:{}(ms),sql:{},parameters:{}", elapsed, sql, sqlParameter);
            } else {
                log.error("!!!!!!execute sql slow,elapsed:{}(ms),sql:{},parameters:{},error:{}", elapsed, sql, sqlParameter, exception.getMessage(), exception);
            }
        }
    }
    private String getSQLParameter(List<List<SQLParameter>> sqlParameters) {
        if (EasyCollectionUtil.isEmpty(sqlParameters)) {
            return null;
        }
        if (sqlParameters.size() == 1) {
            return EasySQLUtil.sqlParameterToString(sqlParameters.get(0));
        }
        StringBuilder sb = new StringBuilder();
        for (List<SQLParameter> sqlParameter : sqlParameters) {
            sb.append(EasySQLUtil.sqlParameterToString(sqlParameter)).append("\n");
        }
        return sb.toString();
    }
}
//依赖注入
public class MyStarterConfigurer implements StarterConfigurer {
    private final DbProperties dbProperties;
    public MyStarterConfigurer(DbProperties dbProperties) {
        this.dbProperties = dbProperties;
    }
    @Override
    public void configure(ServiceCollection services) {
        services.addService(JdbcExecutorListener.class, new LogSlowSQLListener(dbProperties.getSlowMillis()));
    }
}