[jOOQ/jOOQ#8334] Documented caching of ParsingConnection

This commit is contained in:
Lukas Eder 2021-03-22 12:06:08 +01:00
parent 8f7b51808e
commit 50825f5e1b
11 changed files with 278 additions and 20 deletions

View File

@ -0,0 +1,54 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq;
import org.jooq.impl.CacheType;
/**
* The parameter object passed to the
* {@link CacheProvider#provide(CacheContext)} method.
*
* @author Lukas Eder
*/
public interface CacheContext extends Scope {
/**
* The cache type for which a cache should be provided.
*/
CacheType cacheType();
}

View File

@ -63,5 +63,5 @@ public interface CacheProvider {
* A <code>null</code> cache effectively turns off caching for the key.
*/
@Nullable
Map<Object, Object> provide(CacheType key);
Map<Object, Object> provide(CacheContext context);
}

View File

@ -103,6 +103,7 @@ import org.jooq.exception.InvalidResultException;
import org.jooq.exception.MappingException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.TooManyRowsException;
import org.jooq.impl.CacheType;
import org.jooq.impl.DSL;
import org.jooq.impl.ParserException;
import org.jooq.impl.ThreadLocalTransactionProvider;
@ -195,9 +196,23 @@ public interface DSLContext extends Scope {
* A JDBC connection that runs each statement through the {@link #parser()}
* first, prior to re-generating and running the SQL.
* <p>
* Static statements are translated eagerly upon execution, e.g. of
* {@link java.sql.Statement#executeQuery(String)}. Prepared statements are
* prepared lazily once all bind variables are available, because the
* specific bind value and type may influence the generated SQL. As such, a
* {@link PreparedStatement} created from a parsing connection does not yet
* allocate any server side resources until it is executed for the first
* time.
* <p>
* The {@link Configuration#cacheProvider()} is called for
* {@link CacheType#CACHE_PARSING_CONNECTION} to provide a translation cache
* to avoid the overhead of re-parsing and re-generating the same SQL string
* all the time. By default, this is an LRU cache.
* <p>
* The resulting {@link Connection} wraps an underlying JDBC connection that
* has been obtained from {@link ConnectionProvider#acquire()} and must be
* released by calling {@link Connection#close()}.
* released by calling {@link Connection#close()}, which calls
* {@link ConnectionProvider#release(Connection)}.
*/
@NotNull
Connection parsingConnection();

View File

@ -174,6 +174,10 @@ public class Settings
@XmlElement(defaultValue = "true")
protected Boolean cacheRecordMappers = true;
@XmlElement(defaultValue = "true")
protected Boolean cacheParsingConnection = true;
@XmlElement(defaultValue = "8192")
protected Integer cacheParsingConnectionLRUCacheSize = 8192;
@XmlElement(defaultValue = "true")
protected Boolean cachePreparedStatementInLoader = true;
@XmlElement(defaultValue = "THROW_ALL")
@XmlSchemaType(name = "string")
@ -1544,6 +1548,46 @@ public class Settings
this.cacheRecordMappers = value;
}
/**
* Whether parsing connection translations should be cached in the configuration.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isCacheParsingConnection() {
return cacheParsingConnection;
}
/**
* Sets the value of the cacheParsingConnection property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setCacheParsingConnection(Boolean value) {
this.cacheParsingConnection = value;
}
/**
* The default implementation of the ParsingConnection cache's LRU cache size.
*
*/
public Integer getCacheParsingConnectionLRUCacheSize() {
return cacheParsingConnectionLRUCacheSize;
}
/**
* The default implementation of the ParsingConnection cache's LRU cache size.
*
*/
public void setCacheParsingConnectionLRUCacheSize(Integer value) {
this.cacheParsingConnectionLRUCacheSize = value;
}
/**
* Whether JDBC {@link java.sql.PreparedStatement} instances should be cached in loader API.
*
@ -2986,6 +3030,20 @@ public class Settings
return this;
}
public Settings withCacheParsingConnection(Boolean value) {
setCacheParsingConnection(value);
return this;
}
/**
* The default implementation of the ParsingConnection cache's LRU cache size.
*
*/
public Settings withCacheParsingConnectionLRUCacheSize(Integer value) {
setCacheParsingConnectionLRUCacheSize(value);
return this;
}
public Settings withCachePreparedStatementInLoader(Boolean value) {
setCachePreparedStatementInLoader(value);
return this;
@ -3437,6 +3495,8 @@ public class Settings
builder.append("updatablePrimaryKeys", updatablePrimaryKeys);
builder.append("reflectionCaching", reflectionCaching);
builder.append("cacheRecordMappers", cacheRecordMappers);
builder.append("cacheParsingConnection", cacheParsingConnection);
builder.append("cacheParsingConnectionLRUCacheSize", cacheParsingConnectionLRUCacheSize);
builder.append("cachePreparedStatementInLoader", cachePreparedStatementInLoader);
builder.append("throwExceptions", throwExceptions);
builder.append("fetchWarnings", fetchWarnings);
@ -4003,6 +4063,24 @@ public class Settings
return false;
}
}
if (cacheParsingConnection == null) {
if (other.cacheParsingConnection!= null) {
return false;
}
} else {
if (!cacheParsingConnection.equals(other.cacheParsingConnection)) {
return false;
}
}
if (cacheParsingConnectionLRUCacheSize == null) {
if (other.cacheParsingConnectionLRUCacheSize!= null) {
return false;
}
} else {
if (!cacheParsingConnectionLRUCacheSize.equals(other.cacheParsingConnectionLRUCacheSize)) {
return false;
}
}
if (cachePreparedStatementInLoader == null) {
if (other.cachePreparedStatementInLoader!= null) {
return false;
@ -4515,6 +4593,8 @@ 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)+((cacheParsingConnection == null)? 0 :cacheParsingConnection.hashCode()));
result = ((prime*result)+((cacheParsingConnectionLRUCacheSize == null)? 0 :cacheParsingConnectionLRUCacheSize.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()));

View File

@ -164,12 +164,26 @@ public final class SettingsTools {
}
/**
* Whether primary keys should be updatable.
* Whether reflection caching is active.
*/
public static final boolean reflectionCaching(Settings settings) {
return defaultIfNull(settings.isReflectionCaching(), true);
}
/**
* Whether record mapper caching is active.
*/
public static final boolean recordMapperCaching(Settings settings) {
return defaultIfNull(settings.isCacheRecordMappers(), true);
}
/**
* Whether parsing connection caching is active.
*/
public static final boolean parsingConnectionCaching(Settings settings) {
return defaultIfNull(settings.isCacheParsingConnection(), true);
}
/**
* The render locale that is applicable, or the default locale if no such
* locale is configured.

View File

@ -38,11 +38,19 @@
package org.jooq.impl;
import static org.jooq.impl.CacheType.CacheCategory.PARSING_CONNECTION;
import static org.jooq.impl.CacheType.CacheCategory.RECORD_MAPPER;
import static org.jooq.impl.CacheType.CacheCategory.REFLECTION;
import java.util.function.Predicate;
import org.jooq.CacheProvider;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.RecordMapper;
import org.jooq.RecordType;
import org.jooq.conf.Settings;
import org.jooq.conf.SettingsTools;
/**
* The set of internal cache types.
@ -64,60 +72,74 @@ public enum CacheType {
* A reflection cache for lookups of JPA annotated getters in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_ANNOTATED_GETTER("org.jooq.configuration.reflection-cache.get-annotated-getter"),
REFLECTION_CACHE_GET_ANNOTATED_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-getter"),
/**
* A reflection cache for lookups of JPA annotated members in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_ANNOTATED_MEMBERS("org.jooq.configuration.reflection-cache.get-annotated-members"),
REFLECTION_CACHE_GET_ANNOTATED_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-members"),
/**
* A reflection cache for lookups of JPA annotated setters in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_ANNOTATED_SETTERS("org.jooq.configuration.reflection-cache.get-annotated-setters"),
REFLECTION_CACHE_GET_ANNOTATED_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-setters"),
/**
* A reflection cache for lookups of getters matched by name in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_MATCHING_GETTER("org.jooq.configuration.reflection-cache.get-matching-getter"),
REFLECTION_CACHE_GET_MATCHING_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-getter"),
/**
* A reflection cache for lookups of members matched by name in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_MATCHING_MEMBERS("org.jooq.configuration.reflection-cache.get-matching-members"),
REFLECTION_CACHE_GET_MATCHING_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-members"),
/**
* A reflection cache for lookups of setters matched by name in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_GET_MATCHING_SETTERS("org.jooq.configuration.reflection-cache.get-matching-setters"),
REFLECTION_CACHE_GET_MATCHING_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-setters"),
/**
* A reflection cache to check if a type has any JPA annotations at all, in
* {@link DefaultRecordMapper}.
*/
REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS("org.jooq.configuration.reflection-cache.has-column-annotations"),
REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS(REFLECTION, "org.jooq.configuration.reflection-cache.has-column-annotations"),
/**
* A cache used by the {@link DefaultRecordMapperProvider} to cache all
* {@link RecordMapper} instances and their possibly expensive
* initialisations per {@link RecordType} and {@link Class} pairs.
*/
CACHE_RECORD_MAPPERS("org.jooq.configuration.cache.record-mappers"),
CACHE_RECORD_MAPPERS(RECORD_MAPPER, "org.jooq.configuration.cache.record-mappers"),
/**
* [#8334] A cache for SQL to SQL translations in the
* {@link DSLContext#parsingConnection()}, to speed up its usage.
*/
CACHE_PARSING_CONNECTION("org.jooq.configuration.cache.parsing-connection");
CACHE_PARSING_CONNECTION(PARSING_CONNECTION, "org.jooq.configuration.cache.parsing-connection");
final String key;
final CacheCategory category;
final String key;
CacheType(String key) {
CacheType(CacheCategory category, String key) {
this.category = category;
this.key = key;
}
enum CacheCategory {
REFLECTION(SettingsTools::reflectionCaching),
RECORD_MAPPER(SettingsTools::recordMapperCaching),
PARSING_CONNECTION(SettingsTools::parsingConnectionCaching);
final Predicate<? super Settings> predicate;
CacheCategory(Predicate<? super Settings> predicate) {
this.predicate = predicate;
}
}
}

View File

@ -407,7 +407,6 @@ import org.jooq.XMLAttributes;
import org.jooq.XMLExistsPassingStep;
import org.jooq.XMLQueryPassingStep;
import org.jooq.XMLTablePassingStep;
import org.jooq.conf.ParamType;
import org.jooq.conf.Settings;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.impl.XMLParse.DocumentOrContent;

View File

@ -0,0 +1,60 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import org.jooq.CacheContext;
import org.jooq.Configuration;
/**
* @author Lukas Eder
*/
final class DefaultCacheContext extends AbstractScope implements CacheContext {
private final CacheType cacheType;
DefaultCacheContext(Configuration configuration, CacheType cacheType) {
super(configuration);
this.cacheType = cacheType;
}
@Override
public final CacheType cacheType() {
return cacheType;
}
}

View File

@ -38,10 +38,13 @@
package org.jooq.impl;
import static java.util.Collections.synchronizedMap;
import static org.jooq.impl.Tools.settings;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jooq.CacheContext;
import org.jooq.CacheProvider;
/**
@ -53,13 +56,13 @@ import org.jooq.CacheProvider;
final class DefaultCacheProvider implements CacheProvider {
@Override
public Map<Object, Object> provide(CacheType key) {
switch (key) {
public Map<Object, Object> provide(CacheContext ctx) {
switch (ctx.cacheType()) {
// TODO: Is there a better implementation than wrapping LinkedHashMap
// in synchronizedMap(), i.e. one that does not use a monitor?
case CACHE_PARSING_CONNECTION:
return synchronizedMap(new LRUCache<>(1024));
return synchronizedMap(new LRUCache<>(defaultIfNull(settings(ctx.configuration()).getCacheParsingConnectionLRUCacheSize(), 8912)));
default:
return new ConcurrentHashMap<>();

View File

@ -3338,7 +3338,7 @@ final class Tools {
configuration = new DefaultConfiguration();
// Shortcut caching when the relevant Settings flag isn't set.
if (!reflectionCaching(configuration.settings()))
if (!type.category.predicate.test(configuration.settings()))
return operation.get();
Object cacheOrNull = configuration.data(type);
@ -3347,7 +3347,10 @@ final class Tools {
cacheOrNull = configuration.data(type);
if (cacheOrNull == null)
configuration.data(type, cacheOrNull = defaultIfNull(configuration.cacheProvider().provide(type), NULL));
configuration.data(type, cacheOrNull = defaultIfNull(
configuration.cacheProvider().provide(new DefaultCacheContext(configuration, type)),
NULL
));
}
}

View File

@ -387,6 +387,14 @@ 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="cacheParsingConnection" type="boolean" minOccurs="0" maxOccurs="1" default="true">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether parsing connection translations should be cached in the configuration.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="cacheParsingConnectionLRUCacheSize" type="int" minOccurs="0" maxOccurs="1" default="8192">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The default implementation of the ParsingConnection cache's LRU cache size.]]></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>