diff --git a/jOOQ/src/main/java/org/jooq/CacheContext.java b/jOOQ/src/main/java/org/jooq/CacheContext.java new file mode 100644 index 0000000000..9ec4d53249 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/CacheContext.java @@ -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(); +} diff --git a/jOOQ/src/main/java/org/jooq/CacheProvider.java b/jOOQ/src/main/java/org/jooq/CacheProvider.java index 28435cc9fc..e3854af76a 100644 --- a/jOOQ/src/main/java/org/jooq/CacheProvider.java +++ b/jOOQ/src/main/java/org/jooq/CacheProvider.java @@ -63,5 +63,5 @@ public interface CacheProvider { * A null cache effectively turns off caching for the key. */ @Nullable - Map provide(CacheType key); + Map provide(CacheContext context); } diff --git a/jOOQ/src/main/java/org/jooq/DSLContext.java b/jOOQ/src/main/java/org/jooq/DSLContext.java index a9977dd258..9f91165989 100644 --- a/jOOQ/src/main/java/org/jooq/DSLContext.java +++ b/jOOQ/src/main/java/org/jooq/DSLContext.java @@ -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. *

+ * 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. + *

+ * 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. + *

* 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(); diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 9673bd32ab..0de0de62cb 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -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())); diff --git a/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java b/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java index 9ef505658b..93cec23740 100644 --- a/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java +++ b/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java @@ -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. diff --git a/jOOQ/src/main/java/org/jooq/impl/CacheType.java b/jOOQ/src/main/java/org/jooq/impl/CacheType.java index ebcc02d6d3..f010cdc35c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CacheType.java +++ b/jOOQ/src/main/java/org/jooq/impl/CacheType.java @@ -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 predicate; + + CacheCategory(Predicate predicate) { + this.predicate = predicate; + } + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 37f82488ab..709c008e07 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -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; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultCacheContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultCacheContext.java new file mode 100644 index 0000000000..32d29c52e2 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultCacheContext.java @@ -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; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultCacheProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultCacheProvider.java index a37bb1d7a3..e18111a2a3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultCacheProvider.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultCacheProvider.java @@ -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 provide(CacheType key) { - switch (key) { + public Map 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<>(); diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 72bb188697..761aa7cfcb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -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 + )); } } diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd index 9407899197..61f5bead80 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd @@ -387,6 +387,14 @@ UpdatableRecord.store() and UpdatableRecord.update().]]> + + + + + + + +