如何在JOOQ的“ExecuteListener”中安全地执行自定义语句?

时间:2017-11-21 14:59:11

标签: java sql postgresql jdbc jooq

我有一个自定义ExecuteListener,它在JOOQ语句当前正在查看之前执行其他语句:

@Override
public void executeStart(ExecuteContext ctx) {
    if (ctx.type() != READ) {
        Timestamp nowTimestamp = Timestamp.from(Instant.now());
        UUID user = auditFields.currentUserId(); // NOT the Postgres user!

        Connection conn = ctx.connection();
        try (Statement auditUserStmt = conn.createStatement();
             Statement auditTimestampStmt = conn.createStatement()) {

            // hand down context variables to Postgres via SET LOCAL:
            auditUserStmt.execute(format("SET LOCAL audit.AUDIT_USER = '%s'", user.toString()));
            auditTimestampStmt.execute(format("SET LOCAL audit.AUDIT_TIMESTAMP = '%s'", nowTimestamp.toString()));
        }
    }
}

目标是提供一些DB-Triggers用于审计上下文信息。触发器代码如下[1]所示,为您提供一个想法。请注意执行后关闭另外两个try-with-resources的{​​{1}}。

此代码在应用程序服务器中正常工作,我们使用JOOQ的Statement和普通的JOOQ查询(使用DSL),没有原始文本查询。

但是,在使用DefaultConnectionProvider的迁移代码中,当JOOQ尝试执行其INSERT / UPDATE查询时,连接已经关闭。

触发异常的INSERT如下所示:

DataSourceConnectionProvider

这是引发的例外:

String sql = String.format("INSERT INTO migration.migration_journal (id, type, state) values ('%s', 'IDD', 'SUCCESS')", UUID.randomUUID());
dslContext.execute(sql);

我将其追溯到Exception in thread "main" com.my.project.data.exception.RepositoryException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed. at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:51) at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.addIDDJournalSuccessEntry(JooqMigrationJournalRepositoryUtil.java:10) at com.my.project.demo.data.demodata.DemoDbInitializer.execute(DemoDbInitializer.java:46) at com.my.project.shared.data.dbinit.AbstractDbInitializer.execute(AbstractDbInitializer.java:41) at com.my.project.demo.data.demodata.DemoDbInitializer.main(DemoDbInitializer.java:51) Caused by: org.jooq.exception.DataAccessException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed. at org.jooq.impl.Tools.translate(Tools.java:1690) at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:660) at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:354) at org.jooq.impl.DefaultDSLContext.execute(DefaultDSLContext.java:736) at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.lambda$addIDDJournalSuccessEntry$0(JooqMigrationJournalRepositoryUtil.java:12) at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:49) ... 4 more Caused by: org.postgresql.util.PSQLException: This statement has been closed. at org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:647) at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:163) at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:158) at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java) at org.jooq.tools.jdbc.DefaultPreparedStatement.execute(DefaultPreparedStatement.java:194) at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:408) at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:340) ... 7 more ,因此通过DataSourceConnectionProvider.release()调用connection.close()。请注意,auditUserStmt.close()命令在同一SET上执行至关重要。我很乐意从JOOQ的连接中获得一个我必须关闭自己的声明,但是我无法找到一个JOOQ方法来获得这样一个" unmanaged"言。

我们正在使用Hikari连接池,因此JOOQ获取的连接是Connection。在迁移代码中,HikariProxyConnection仅配置为最低限度:

DataSource

如何修复HikariDataSource dataSource = new HikariDataSource(); dataSource.setPoolName(poolName); dataSource.setJdbcUrl(serverUrl); dataSource.setUsername(user); dataSource.setPassword(password); dataSource.setMaximumPoolSize(10);

我使用的是Postgres JDBC Driver 42.1.1的JOOQ 3.7.3和Postgres 9.5。

[1]:Postgres触发代码:

ExecuteListener

1 个答案:

答案 0 :(得分:2)

根据@LukasEder的建议,我最终用JDBC Connection而不是ExecuteListener的包装来解决这个问题。

此方法的主要复杂之处在于JDBC不提供跟踪事务状态的任何内容,因此每次提交或回滚事务时,连接包装器都需要重新设置上下文信息。

我在this gist中记录了我的完整解决方案,因为它太长而无法适应SO答案。