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