[jOOQ/jOOQ#10432] Add Settings.cachePreparedStatementsInLoader to keeping open PreparedStatements in Loader API

This commit is contained in:
Lukas Eder 2020-07-22 15:34:19 +02:00
parent 06f15008cc
commit 9e3a1dc5ec
6 changed files with 141 additions and 37 deletions

View File

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

View File

@ -84,8 +84,6 @@ final class JSONReader {
@SuppressWarnings("rawtypes")
final Result<Record> read(final Reader reader) {
try {
@SuppressWarnings("rawtypes")
Object root = new JSONParser().parse(reader, new ContainerFactory() {
@Override
public Map createObjectContainer() {

View File

@ -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<R extends Record> implements
// Configuration data
// ------------------
private final DSLContext create;
private final Configuration configuration;
private final Table<R> table;
private int onDuplicate = ON_DUPLICATE_KEY_ERROR;
@ -180,7 +188,6 @@ final class LoaderImpl<R extends Record> implements
private final List<LoaderError> errors;
LoaderImpl(Configuration configuration, Table<R> table) {
this.create = DSL.using(configuration);
this.configuration = configuration;
this.table = table;
this.errors = new ArrayList<>();
@ -669,12 +676,12 @@ final class LoaderImpl<R extends Record> 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<Record> r = new JSONReader(create).read(reader);
Result<Record> 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<R extends Record> implements
List<Object[]> 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<R extends Record> 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<String, CachedPS> 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<? extends Object[]> 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<? extends Object[]> 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<? extends Object[]> iterator, DSLContext ctx) throws SQLException {
Object[] row = null;
BatchBindStep bind = null;
InsertQuery<R> insert = null;
@ -769,7 +825,7 @@ final class LoaderImpl<R extends Record> implements
buffered++;
if (insert == null)
insert = create.insertQuery(table);
insert = ctx.insertQuery(table);
if (newRecord) {
newRecord = false;
@ -811,7 +867,7 @@ final class LoaderImpl<R extends Record> 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<R extends Record> 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)

View File

@ -1247,7 +1247,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
? new Field[] { DSL.field("*") }
: Tools.aliasedFields(originalFields),
null
(Field<?>) null
);
alternativeFields[alternativeFields.length - 1] =

View File

@ -2723,12 +2723,16 @@ final class Tools {
}
@SuppressWarnings("unchecked")
static final <T> T[] combine(T[] array, T value) {
static final <T> 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> T[] combine(T[] array, T value) {
T[] result = Arrays.copyOf(array, array.length + 1);;
result[array.length] = value;
return result;
}

View File

@ -341,6 +341,10 @@ UpdatableRecord.store() and UpdatableRecord.update().]]></jxb:javadoc></jxb:prop
<element name="cacheRecordMappers" type="boolean" minOccurs="0" maxOccurs="1" default="true">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether record mappers should be cached in the configuration.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="cachePreparedStatementInLoader" type="boolean" minOccurs="0" maxOccurs="1" default="true">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether JDBC {@link java.sql.PreparedStatement} instances should be cached in loader API.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="throwExceptions" type="jooq-runtime:ThrowExceptions" minOccurs="0" maxOccurs="1" default="THROW_ALL">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[A strategy defining how exceptions from the database / JDBC driver should be propagated]]></jxb:javadoc></jxb:property></appinfo></annotation>