[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.
This commit is contained in:
Lukas Eder 2020-07-22 13:11:57 +02:00
parent b29e2aeefc
commit 2da86fde7a
7 changed files with 66 additions and 38 deletions

View File

@ -383,6 +383,26 @@ public interface ExecuteListener extends EventListener, Serializable {
* <li>{@link ExecuteContext#sql(String)}: The rendered <code>SQL</code>
* statement that is about to be executed. You can modify this statement
* freely.</li>
* <li>{@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.
* <p>
* A custom {@link PreparedStatement} needs to take into account
* {@link Settings#getStatementType()}, and avoid bind variable markers for
* {@link StatementType#STATIC_STATEMENT}.
* <p>
* 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.
* <p>
* 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.</li>
* </ul>
*/
void prepareStart(ExecuteContext ctx);

View File

@ -829,6 +829,7 @@ abstract class AbstractDMLQuery<R extends Record> extends AbstractRowCountQuery
if (returning.isEmpty()) {
super.prepare(ctx);
}
@ -881,7 +882,8 @@ abstract class AbstractDMLQuery<R extends Record> 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<R extends Record> 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;
}
}

View File

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

View File

@ -238,34 +238,33 @@ abstract class AbstractResultQuery<R extends Record> 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

View File

@ -504,7 +504,8 @@ public abstract class AbstractRoutine<T> 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);

View File

@ -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++) {

View File

@ -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