From 2da86fde7ae669d1730286bad8285a59c0b45eb3 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 22 Jul 2020 13:11:57 +0200 Subject: [PATCH] [jOOQ/jOOQ#2752] Allow for skipping preparing statement. If after ExecuteListener::prepareStart, ExecuteContext::statement is available, use that statement instead of preparing a new one. --- .../main/java/org/jooq/ExecuteListener.java | 20 +++++++++ .../java/org/jooq/impl/AbstractDMLQuery.java | 41 +++++++++++-------- .../java/org/jooq/impl/AbstractQuery.java | 3 +- .../org/jooq/impl/AbstractResultQuery.java | 31 +++++++------- .../java/org/jooq/impl/AbstractRoutine.java | 3 +- .../java/org/jooq/impl/BatchMultiple.java | 3 +- .../main/java/org/jooq/impl/BatchSingle.java | 3 +- 7 files changed, 66 insertions(+), 38 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/ExecuteListener.java b/jOOQ/src/main/java/org/jooq/ExecuteListener.java index 37cea53554..6662957893 100644 --- a/jOOQ/src/main/java/org/jooq/ExecuteListener.java +++ b/jOOQ/src/main/java/org/jooq/ExecuteListener.java @@ -383,6 +383,26 @@ public interface ExecuteListener extends EventListener, Serializable { *
  • {@link ExecuteContext#sql(String)}: The rendered SQL * statement that is about to be executed. You can modify this statement * freely.
  • + *
  • {@link ExecuteContext#statement()}: The {@link PreparedStatement} + * about to be executed. At this stage, no such statement is available yet, + * but if provided, the execution lifecycle will skip preparing a statement. + * This can be used e.g. to implement a transaction-bound prepared statement + * cache. + *

    + * A custom {@link PreparedStatement} needs to take into account + * {@link Settings#getStatementType()}, and avoid bind variable markers for + * {@link StatementType#STATIC_STATEMENT}. + *

    + * Flags such as {@link Query#queryTimeout(int)}, + * {@link Query#poolable(boolean)}, {@link ResultQuery#maxRows(int)}, which + * correspond to mutable flags on a {@link PreparedStatement}, are set by + * jOOQ even if a listener provides the statement. + *

    + * Flags such as {@link ResultQuery#resultSetConcurrency(int)}, + * {@link ResultQuery#resultSetHoldability(int)}, + * {@link ResultQuery#resultSetType(int)}, which correspond to immutable + * flags that are set on the statement at statement creation are not set on + * a statement provided by a listener.

  • * */ void prepareStart(ExecuteContext ctx); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractDMLQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractDMLQuery.java index edcc81f5fc..73d09d09b7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractDMLQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractDMLQuery.java @@ -829,6 +829,7 @@ abstract class AbstractDMLQuery extends AbstractRowCountQuery + if (returning.isEmpty()) { super.prepare(ctx); } @@ -881,7 +882,8 @@ abstract class AbstractDMLQuery extends AbstractRowCountQuery // yet, or UPDATE .. RETURNING case MARIADB: case MYSQL: - ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS)); + if (ctx.statement() == null) + ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS)); break; // The default is to return all requested fields directly @@ -897,27 +899,30 @@ abstract class AbstractDMLQuery extends AbstractRowCountQuery + case HSQLDB: default: { - String[] names = new String[returningResolvedAsterisks.size()]; - RenderNameCase style = SettingsTools.getRenderNameCase(configuration().settings()); + if (ctx.statement() == null) { + String[] names = new String[returningResolvedAsterisks.size()]; + RenderNameCase style = SettingsTools.getRenderNameCase(configuration().settings()); - // [#2845] Field names should be passed to JDBC in the case - // imposed by the user. For instance, if the user uses - // PostgreSQL generated case-insensitive Fields (default to lower case) - // and wants to query HSQLDB (default to upper case), they may choose - // to overwrite casing using RenderNameCase. - if (style == RenderNameCase.UPPER) - for (int i = 0; i < names.length; i++) - names[i] = returningResolvedAsterisks.get(i).getName().toUpperCase(renderLocale(configuration().settings())); - else if (style == RenderNameCase.LOWER) - for (int i = 0; i < names.length; i++) - names[i] = returningResolvedAsterisks.get(i).getName().toLowerCase(renderLocale(configuration().settings())); - else - for (int i = 0; i < names.length; i++) - names[i] = returningResolvedAsterisks.get(i).getName(); + // [#2845] Field names should be passed to JDBC in the case + // imposed by the user. For instance, if the user uses + // PostgreSQL generated case-insensitive Fields (default to lower case) + // and wants to query HSQLDB (default to upper case), they may choose + // to overwrite casing using RenderNameCase. + if (style == RenderNameCase.UPPER) + for (int i = 0; i < names.length; i++) + names[i] = returningResolvedAsterisks.get(i).getName().toUpperCase(renderLocale(configuration().settings())); + else if (style == RenderNameCase.LOWER) + for (int i = 0; i < names.length; i++) + names[i] = returningResolvedAsterisks.get(i).getName().toLowerCase(renderLocale(configuration().settings())); + else + for (int i = 0; i < names.length; i++) + names[i] = returningResolvedAsterisks.get(i).getName(); + ctx.statement(connection.prepareStatement(ctx.sql(), names)); + } - ctx.statement(connection.prepareStatement(ctx.sql(), names)); break; } } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java index 79800ba5c9..23ccf8bae4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java @@ -434,7 +434,8 @@ abstract class AbstractQuery extends AbstractQueryPart implements Query { * this method. */ protected void prepare(ExecuteContext ctx) throws SQLException { - ctx.statement(ctx.connection().prepareStatement(ctx.sql())); + if (ctx.statement() == null) + ctx.statement(ctx.connection().prepareStatement(ctx.sql())); } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java index 7513f076bb..2842fb50a0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java @@ -238,34 +238,33 @@ abstract class AbstractResultQuery extends AbstractQuery imple @Override protected final void prepare(ExecuteContext ctx) throws SQLException { + if (ctx.statement() == null) { - // [#1846] [#2265] [#2299] Users may explicitly specify how ResultSets - // created by jOOQ behave. This will override any other default behaviour - if (resultSetConcurrency != 0 || resultSetType != 0 || resultSetHoldability != 0) { - int type = resultSetType != 0 ? resultSetType : ResultSet.TYPE_FORWARD_ONLY; - int concurrency = resultSetConcurrency != 0 ? resultSetConcurrency : ResultSet.CONCUR_READ_ONLY; + // [#1846] [#2265] [#2299] Users may explicitly specify how ResultSets + // created by jOOQ behave. This will override any other default behaviour + if (resultSetConcurrency != 0 || resultSetType != 0 || resultSetHoldability != 0) { + int type = resultSetType != 0 ? resultSetType : ResultSet.TYPE_FORWARD_ONLY; + int concurrency = resultSetConcurrency != 0 ? resultSetConcurrency : ResultSet.CONCUR_READ_ONLY; - // Sybase doesn't support holdability. Avoid setting it! - if (resultSetHoldability == 0) { - ctx.statement(ctx.connection().prepareStatement(ctx.sql(), type, concurrency)); + // Sybase doesn't support holdability. Avoid setting it! + if (resultSetHoldability == 0) + ctx.statement(ctx.connection().prepareStatement(ctx.sql(), type, concurrency)); + else + ctx.statement(ctx.connection().prepareStatement(ctx.sql(), type, concurrency, resultSetHoldability)); } + + // Regular behaviour else { - ctx.statement(ctx.connection().prepareStatement(ctx.sql(), type, concurrency, resultSetHoldability)); + ctx.statement(ctx.connection().prepareStatement(ctx.sql())); } } - // Regular behaviour - else { - ctx.statement(ctx.connection().prepareStatement(ctx.sql())); - } - Tools.setFetchSize(ctx, fetchSize); // [#1854] [#4753] Set the max number of rows for this result query int m = SettingsTools.getMaxRows(maxRows, ctx.settings()); - if (m != 0) { + if (m != 0) ctx.statement().setMaxRows(m); - } } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java index 588c5e5caf..b872cc1a9b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java @@ -504,7 +504,8 @@ public abstract class AbstractRoutine extends AbstractNamed implements Routin listener.renderEnd(ctx); listener.prepareStart(ctx); - ctx.statement(connection.prepareCall(ctx.sql())); + if (ctx.statement() == null) + ctx.statement(connection.prepareCall(ctx.sql())); Tools.setFetchSize(ctx, 0); // [#1856] TODO: Add Statement flags like timeout here listener.prepareEnd(ctx); diff --git a/jOOQ/src/main/java/org/jooq/impl/BatchMultiple.java b/jOOQ/src/main/java/org/jooq/impl/BatchMultiple.java index 876b68f4bc..d93f4172ea 100644 --- a/jOOQ/src/main/java/org/jooq/impl/BatchMultiple.java +++ b/jOOQ/src/main/java/org/jooq/impl/BatchMultiple.java @@ -85,7 +85,8 @@ final class BatchMultiple extends AbstractBatch { // [#8968] Keep start() event inside of lifecycle management listener.start(ctx); - ctx.statement(new SettingsEnabledPreparedStatement(connection)); + if (ctx.statement() == null) + ctx.statement(new SettingsEnabledPreparedStatement(connection)); String[] batchSQL = ctx.batchSQL(); for (int i = 0; i < queries.length; i++) { diff --git a/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java b/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java index 9b40a30e07..bf250b7022 100644 --- a/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java +++ b/jOOQ/src/main/java/org/jooq/impl/BatchSingle.java @@ -205,7 +205,8 @@ final class BatchSingle extends AbstractBatch implements BatchBindStep { listener.renderEnd(ctx); listener.prepareStart(ctx); - ctx.statement(connection.prepareStatement(ctx.sql())); + if (ctx.statement() == null) + ctx.statement(connection.prepareStatement(ctx.sql())); listener.prepareEnd(ctx); // [#9295] use query timeout from settings