From 9e3a1dc5ec53775584ae44fb949a5791c660fa4a Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 22 Jul 2020 15:34:19 +0200 Subject: [PATCH] [jOOQ/jOOQ#10432] Add Settings.cachePreparedStatementsInLoader to keeping open PreparedStatements in Loader API --- .../src/main/java/org/jooq/conf/Settings.java | 42 +++++++ .../main/java/org/jooq/impl/JSONReader.java | 2 - .../main/java/org/jooq/impl/LoaderImpl.java | 118 +++++++++++++----- .../java/org/jooq/impl/SelectQueryImpl.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 10 +- .../resources/xsd/jooq-runtime-3.14.0.xsd | 4 + 6 files changed, 141 insertions(+), 37 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 0f9b8a57e9..d5b51c0381 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -158,6 +158,8 @@ public class Settings protected Boolean reflectionCaching = true; @XmlElement(defaultValue = "true") protected Boolean cacheRecordMappers = true; + @XmlElement(defaultValue = "true") + protected Boolean cachePreparedStatementInLoader = true; @XmlElement(defaultValue = "THROW_ALL") @XmlSchemaType(name = "string") protected ThrowExceptions throwExceptions = ThrowExceptions.THROW_ALL; @@ -1363,6 +1365,30 @@ public class Settings this.cacheRecordMappers = value; } + /** + * Whether JDBC {@link java.sql.PreparedStatement} instances should be cached in loader API. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isCachePreparedStatementInLoader() { + return cachePreparedStatementInLoader; + } + + /** + * Sets the value of the cachePreparedStatementInLoader property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setCachePreparedStatementInLoader(Boolean value) { + this.cachePreparedStatementInLoader = value; + } + /** * A strategy defining how exceptions from the database / JDBC driver should be propagated * @@ -2563,6 +2589,11 @@ public class Settings return this; } + public Settings withCachePreparedStatementInLoader(Boolean value) { + setCachePreparedStatementInLoader(value); + return this; + } + /** * A strategy defining how exceptions from the database / JDBC driver should be propagated * @@ -2943,6 +2974,7 @@ public class Settings builder.append("updatablePrimaryKeys", updatablePrimaryKeys); builder.append("reflectionCaching", reflectionCaching); builder.append("cacheRecordMappers", cacheRecordMappers); + builder.append("cachePreparedStatementInLoader", cachePreparedStatementInLoader); builder.append("throwExceptions", throwExceptions); builder.append("fetchWarnings", fetchWarnings); builder.append("fetchServerOutputSize", fetchServerOutputSize); @@ -3446,6 +3478,15 @@ public class Settings return false; } } + if (cachePreparedStatementInLoader == null) { + if (other.cachePreparedStatementInLoader!= null) { + return false; + } + } else { + if (!cachePreparedStatementInLoader.equals(other.cachePreparedStatementInLoader)) { + return false; + } + } if (throwExceptions == null) { if (other.throwExceptions!= null) { return false; @@ -3871,6 +3912,7 @@ public class Settings result = ((prime*result)+((updatablePrimaryKeys == null)? 0 :updatablePrimaryKeys.hashCode())); result = ((prime*result)+((reflectionCaching == null)? 0 :reflectionCaching.hashCode())); result = ((prime*result)+((cacheRecordMappers == null)? 0 :cacheRecordMappers.hashCode())); + result = ((prime*result)+((cachePreparedStatementInLoader == null)? 0 :cachePreparedStatementInLoader.hashCode())); result = ((prime*result)+((throwExceptions == null)? 0 :throwExceptions.hashCode())); result = ((prime*result)+((fetchWarnings == null)? 0 :fetchWarnings.hashCode())); result = ((prime*result)+((fetchServerOutputSize == null)? 0 :fetchServerOutputSize.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONReader.java b/jOOQ/src/main/java/org/jooq/impl/JSONReader.java index 3cbecf6e32..2a16f81dfa 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONReader.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONReader.java @@ -84,8 +84,6 @@ final class JSONReader { @SuppressWarnings("rawtypes") final Result read(final Reader reader) { try { - - @SuppressWarnings("rawtypes") Object root = new JSONParser().parse(reader, new ContainerFactory() { @Override public Map createObjectContainer() { diff --git a/jOOQ/src/main/java/org/jooq/impl/LoaderImpl.java b/jOOQ/src/main/java/org/jooq/impl/LoaderImpl.java index cd902bf1a6..db4489896e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/LoaderImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/LoaderImpl.java @@ -37,11 +37,14 @@ */ package org.jooq.impl; +import static java.lang.Boolean.FALSE; // ... import static org.jooq.SQLDialect.MARIADB; // ... import static org.jooq.SQLDialect.MYSQL; import static org.jooq.impl.Tools.EMPTY_FIELD; +import static org.jooq.impl.Tools.combine; +import static org.jooq.tools.jdbc.JDBCUtils.safeClose; import java.io.File; import java.io.IOException; @@ -50,13 +53,16 @@ import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -64,7 +70,9 @@ import javax.xml.bind.DatatypeConverter; import org.jooq.BatchBindStep; import org.jooq.Configuration; +import org.jooq.ConnectionRunnable; import org.jooq.DSLContext; +import org.jooq.ExecuteContext; import org.jooq.Field; import org.jooq.InsertQuery; import org.jooq.Loader; @@ -91,6 +99,7 @@ import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; import org.jooq.tools.csv.CSVParser; import org.jooq.tools.csv.CSVReader; +import org.jooq.tools.jdbc.DefaultPreparedStatement; import org.xml.sax.InputSource; @@ -141,7 +150,6 @@ final class LoaderImpl implements // Configuration data // ------------------ - private final DSLContext create; private final Configuration configuration; private final Table table; private int onDuplicate = ON_DUPLICATE_KEY_ERROR; @@ -180,7 +188,6 @@ final class LoaderImpl implements private final List errors; LoaderImpl(Configuration configuration, Table table) { - this.create = DSL.using(configuration); this.configuration = configuration; this.table = table; this.errors = new ArrayList<>(); @@ -669,12 +676,12 @@ final class LoaderImpl implements throw new LoaderConfigurationException("Cannot apply bulk loading with onDuplicateKey flags. Turn off either flag."); } - private final void executeJSON() throws IOException { + private final void executeJSON() { Reader reader = null; try { reader = input.reader(); - Result r = new JSONReader(create).read(reader); + Result r = new JSONReader(configuration.dsl()).read(reader); source = r.fields(); // The current json format is not designed for streaming. Thats why @@ -682,19 +689,12 @@ final class LoaderImpl implements List allRecords = Arrays.asList(r.intoArrays()); executeSQL(allRecords.iterator()); } - - // SQLExceptions originating from rollbacks or commits are always fatal - // They are propagated, and not swallowed - catch (SQLException e) { - throw Tools.translate(null, e); - } finally { - if (reader != null) - reader.close(); + safeClose(reader); } } - private final void executeCSV() throws IOException { + private final void executeCSV() { CSVReader reader = null; try { @@ -708,31 +708,87 @@ final class LoaderImpl implements executeSQL(reader); } - - // SQLExceptions originating from rollbacks or commits are always fatal - // They are propagated, and not swallowed - catch (SQLException e) { - throw Tools.translate(null, e); - } finally { - if (reader != null) - reader.close(); + safeClose(reader); } } private final void executeRows() { - try { - executeSQL(arrays); + executeSQL(arrays); + } + + private static final class CachedPSListener extends DefaultExecuteListener { + + /** + * Generated UID + */ + private static final long serialVersionUID = 1374352882405299310L; + + final Map map = new HashMap<>(); + + @Override + public void prepareStart(ExecuteContext ctx) { + CachedPS ps = map.get(ctx.sql()); + + if (ps != null) + ctx.statement(ps); } - // SQLExceptions originating from rollbacks or commits are always fatal - // They are propagated, and not swallowed - catch (SQLException e) { - throw Tools.translate(null, e); + @Override + public void prepareEnd(ExecuteContext ctx) { + if (!(ctx.statement() instanceof CachedPS)) { + CachedPS ps = new CachedPS(ctx.statement()); + map.put(ctx.sql(), ps); + ctx.statement(ps); + } + } + + public void close() { + for (CachedPS ps : map.values()) + safeClose(ps.getDelegate()); } } - private final void executeSQL(Iterator iterator) throws SQLException { + private static class CachedPS extends DefaultPreparedStatement { + CachedPS(PreparedStatement delegate) { + super(delegate); + } + + @Override + public void close() throws SQLException {} + } + + private final void executeSQL(Iterator iterator) { + configuration.dsl().connection(new ConnectionRunnable() { + @Override + public void run(Connection connection) throws Exception { + Configuration c = configuration.derive(new DefaultConnectionProvider(connection)); + + if (FALSE.equals(c.settings().isCachePreparedStatementInLoader())) { + executeSQL(iterator, c.dsl()); + } + + else { + CachedPSListener cache = new CachedPSListener(); + + try { + executeSQL(iterator, c + .derive(combine(new DefaultExecuteListenerProvider(cache), c.executeListenerProviders())) + .dsl() + ); + } + finally { + try { + cache.close(); + } + catch (Exception e) {} + } + } + } + }); + } + + private final void executeSQL(Iterator iterator, DSLContext ctx) throws SQLException { Object[] row = null; BatchBindStep bind = null; InsertQuery insert = null; @@ -769,7 +825,7 @@ final class LoaderImpl implements buffered++; if (insert == null) - insert = create.insertQuery(table); + insert = ctx.insertQuery(table); if (newRecord) { newRecord = false; @@ -811,7 +867,7 @@ final class LoaderImpl implements if (batch != BATCH_NONE) { if (bind == null) - bind = create.batch(insert); + bind = ctx.batch(insert); bind.bind(insert.getBindValues().toArray()); insert = null; @@ -831,7 +887,7 @@ final class LoaderImpl implements // [#10358] The MySQL dialect category doesn't return rowcounts // in INSERT .. ON DUPLICATE KEY UPDATE statements, but // 1 = INSERT, 2 = UPDATE, instead - if (onDuplicate == ON_DUPLICATE_KEY_UPDATE && NO_SUPPORT_ROWCOUNT_ON_DUPLICATE.contains(create.dialect())) + if (onDuplicate == ON_DUPLICATE_KEY_UPDATE && NO_SUPPORT_ROWCOUNT_ON_DUPLICATE.contains(ctx.dialect())) totalRowCounts = buffered; else for (int rowCount : rowcounts) diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 1813f1e008..24362e0c5b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -1247,7 +1247,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp ? new Field[] { DSL.field("*") } : Tools.aliasedFields(originalFields), - null + (Field) null ); alternativeFields[alternativeFields.length - 1] = diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index e69f1f1c60..44b6c1e009 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -2723,12 +2723,16 @@ final class Tools { } @SuppressWarnings("unchecked") - static final T[] combine(T[] array, T value) { + static final T[] combine(T value, T[] array) { T[] result = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), array.length + 1); + result[0] = value; + System.arraycopy(array, 0, result, 1, array.length); + return result; + } - System.arraycopy(array, 0, result, 0, array.length); + static final T[] combine(T[] array, T value) { + T[] result = Arrays.copyOf(array, array.length + 1);; result[array.length] = value; - return result; } diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd index eff5e22a86..518c2274bb 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd @@ -341,6 +341,10 @@ UpdatableRecord.store() and UpdatableRecord.update().]]> + + + +