How to safely execute custom statements within JOOQ's `ExecuteListener`?

六眼飞鱼酱① 提交于 2021-02-10 05:24:38

问题


I have a custom ExecuteListener that executes additional statements before the statement JOOQ is currently looking at:

@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()));
        }
    }
}

The goal is to provide some DB-Triggers for auditing with context information. The trigger code is given below [1] to give you an idea. Note the try-with-resources that closes the two additional Statements after execution.

This code works fine in the application server, where we use JOOQ's DefaultConnectionProvider and ordinary JOOQ queries (using the DSL), no raw text queries.

However, in the migration code, which uses a DataSourceConnectionProvider, the connection is already closed when JOOQ attempts to execute its INSERT/UPDATE query.

The INSERT that triggers the exception looks like this:

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

and this is the exception raised:

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

I traced this back to DataSourceConnectionProvider.release() and therefore connection.close() being called via auditUserStmt.close(). Note that it is critical that the SET commands are executed on the same Connection. I would be fine with obtaining a Statement from JOOQ's connection that I have to close myself, but I can't find a JOOQ method to obtain such an "unmanaged" statement.

We're using the Hikari connection pool, so the connection acquired by JOOQ is a HikariProxyConnection. From within the migration code, the DataSource is configured only minimally:

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

How can I fix my ExecuteListener?

I am using JOOQ 3.7.3 and Postgres 9.5., with the Postgres JDBC Driver 42.1.1.

[1]: Postgres Trigger Code:

CREATE OR REPLACE FUNCTION set_audit_fields()
  RETURNS TRIGGER AS $$
DECLARE
  audit_user UUID;
BEGIN
  -- Postgres 9.6 will offer current_setting(..., [missing_ok]) which makes the exception handling obsolete.
  BEGIN
    audit_user := current_setting('audit.AUDIT_USER');
    EXCEPTION WHEN OTHERS THEN
    audit_user := NULL;
  END;

  IF TG_OP = 'INSERT'
  THEN
    NEW.inserted_by := audit_user;
  ELSE
    NEW.updated_by  := audit_user;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

回答1:


As per @LukasEder's suggestion, I ended up solving this problem with a wrapper around the JDBC Connection instead of with an ExecuteListener.

The main complication in this approach is that JDBC does not provide anything to track the transaction status, and therefore the connection wrapper needs to re-set the context information every time the transaction was committed or rollbacked.

I documented my full solution in this gist, since it's too long to fit in an SO answer.



来源:https://stackoverflow.com/questions/47416149/how-to-safely-execute-custom-statements-within-jooqs-executelistener

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!