88package com .newrelic .agent .bridge .datastore ;
99
1010import com .newrelic .agent .bridge .AgentBridge ;
11- import com .newrelic .agent .bridge .NoOpTransaction ;
1211import com .newrelic .api .agent .Logger ;
1312import com .newrelic .api .agent .NewRelic ;
1413
@@ -41,10 +40,25 @@ protected Boolean initialValue() {
4140 }
4241 };
4342
43+ //statement cache settings
44+ /**
45+ * In extreme high-throughput scenarios, weak keyed eviction may throttle the CPU when it tries to perform maintenance on statement caches.
46+ * Setting -Dnewrelic.config.jdbc_statement_weak_key_caching.enabled=false will convert the caches to ordinary ConcurrentHashMaps
47+ * to alleviate this maintenance overhead.
48+ * <p>
49+ * Statements are only removed from the caches on Statement.close(), so this config MUST be enabled with caution. If the user does not properly close their
50+ * statements, setting -Dnewrelic.config.jdbc_statement_weak_key_caching.enabled=false will cause memory issues. The user MUST explicitly call
51+ * Statement.close() (not the implicit Statement.closeOnCompletion()) for cleanup to be guaranteed.
52+ */
53+ private static final String JDBC_STATEMENT_WEAK_KEY_CACHING_ENABLED = "jdbc_statement_weak_key_caching.enabled" ;
54+ private static final boolean JDBC_STATEMENT_WEAK_KEY_CACHING_ENABLED_DEFAULT = Boolean .TRUE ;
55+ private static final int INITIAL_STATEMENT_CACHE_CAPACITY = 1024 ;
56+ private static final Map <Statement , Object []> statementToParams = initStatementCache ("statementToParams" );
57+ private static final Map <Statement , String > statementToSql = initStatementCache ("statementToSql" );
58+
4459 // This will contain every vendor type that we detected on the client system
4560 private static final Map <String , DatabaseVendor > typeToVendorLookup = new ConcurrentHashMap <>(10 );
4661 private static final Map <Class <?>, DatabaseVendor > classToVendorLookup = AgentBridge .collectionFactory .createConcurrentWeakKeyedMap ();
47- private static final Map <Statement , String > statementToSql = AgentBridge .collectionFactory .createConcurrentWeakKeyedMap ();
4862 private static final Map <Connection , String > connectionToIdentifier = AgentBridge .collectionFactory .createConcurrentWeakKeyedMap ();
4963 private static final Map <Connection , String > connectionToURL = AgentBridge .collectionFactory .createConcurrentWeakKeyedMap ();
5064 public static final String UNKNOWN = "unknown" ;
@@ -58,6 +72,31 @@ protected Boolean initialValue() {
5872 private static volatile Boolean isSqlMetadataCommentsEnabled = null ;
5973 private static volatile String cachedServiceGuid = null ;
6074
75+ public static Object [] getParams (Statement statement ) {
76+ return statementToParams .get (statement );
77+ }
78+
79+ public static void putParams (Statement statement , Object [] params ) {
80+ statementToParams .put (statement , params );
81+ }
82+
83+ public static String getSql (Statement statement ) {
84+ AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Getting sql for statement: {0}" , statement );
85+ return statementToSql .get (statement );
86+ }
87+
88+ public static void putSql (Statement statement , String sql ) {
89+ AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Storing sql for statement: {0}" , statement );
90+ statementToSql .put (statement , sql );
91+ }
92+
93+ public static void removeStatement (Statement statement ) {
94+ if (statement != null ) {
95+ statementToParams .remove (statement );
96+ statementToSql .remove (statement );
97+ }
98+ }
99+
61100 public static void putVendor (Class <?> driverOrDatastoreClass , DatabaseVendor databaseVendor ) {
62101 classToVendorLookup .put (driverOrDatastoreClass , databaseVendor );
63102 AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Storing class: {0}, vendor: {1}" , driverOrDatastoreClass , databaseVendor );
@@ -68,7 +107,7 @@ public static void putVendor(Class<?> driverOrDatastoreClass, DatabaseVendor dat
68107
69108 public static DatabaseVendor getVendor (Class <?> driverOrDatastoreClass , String url ) {
70109 DatabaseVendor vendor = classToVendorLookup .get (driverOrDatastoreClass );
71- AgentBridge .getAgent ().getLogger ().log (Level .FINEST ,"Getting class: {0}, url: {1}, vendor: {2}" , driverOrDatastoreClass , url , vendor );
110+ AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Getting class: {0}, url: {1}, vendor: {2}" , driverOrDatastoreClass , url , vendor );
72111
73112 if (vendor != null ) {
74113 return vendor ;
@@ -150,16 +189,6 @@ public static String getCachedDatabaseName(Connection connection) {
150189 return null ;
151190 }
152191
153- public static void putSql (Statement statement , String sql ) {
154- AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Storing sql for statement: {0}" , statement );
155- statementToSql .put (statement , sql );
156- }
157-
158- public static String getSql (Statement statement ) {
159- AgentBridge .getAgent ().getLogger ().log (Level .FINEST , "Getting sql for statement: {0}" , statement );
160- return statementToSql .get (statement );
161- }
162-
163192 public static Object [] growParameterArray (Object [] params , int missingIndex ) {
164193 int length = Math .max (10 , (int ) (missingIndex * 1.2 ));
165194 Object [] newParams = new Object [length ];
@@ -224,7 +253,7 @@ public static String getConnectionURL(Connection connection) {
224253 *
225254 * @param connectionString URL connection string to parse.
226255 * @return identifier parsed from connection string if vendor is part of supported in-memory JDBC drivers,
227- * {@link JdbcHelper#UNKNOWN} otherwise.
256+ * {@link JdbcHelper#UNKNOWN} otherwise.
228257 */
229258 public static String parseInMemoryIdentifier (String connectionString ) {
230259 if (connectionString == null ) {
@@ -257,8 +286,6 @@ public static String parseInMemoryIdentifier(String connectionString) {
257286 return UNKNOWN ;
258287 }
259288
260-
261-
262289 /**
263290 * Parse and cache identifier of in-memory database from Connection string url.
264291 *
@@ -325,7 +352,6 @@ public static void invalidateMetadataCommentConfig() {
325352 * the original SQL statement.
326353 *
327354 * @param sql the target SQL statement
328- *
329355 * @return a SQL statement which might have the metadata comment prepended to it
330356 */
331357 public static String addSqlMetadataCommentIfNeeded (String sql ) {
@@ -412,6 +438,29 @@ private static Boolean isSqlMetadataCommentsEnabled() {
412438 return (isSqlMetadataCommentsEnabled != null ) ? isSqlMetadataCommentsEnabled : Boolean .FALSE ;
413439 }
414440
441+ /**
442+ * Checks the config to see whether weak key caching is enabled.
443+ * <p>
444+ * If weak key caching is enabled (default) a Caffeine-backed Weak Keyed cache will be returned.
445+ * If weak key caching is not enabled a vanilla Concurrent Hash Map will be returned.
446+ */
447+ static <V > Map <Statement , V > initStatementCache (String cacheName ) {
448+ boolean weakKeyCachingEnabled = NewRelic .getAgent ()
449+ .getConfig ()
450+ .getValue (JDBC_STATEMENT_WEAK_KEY_CACHING_ENABLED , JDBC_STATEMENT_WEAK_KEY_CACHING_ENABLED_DEFAULT );
451+ if (weakKeyCachingEnabled ) {
452+ NewRelic .getAgent ().getLogger ().log (Level .INFO , "JDBC Statement weak key caching is enabled. Using default Weak Keyed Cache for {0}." , cacheName );
453+ return AgentBridge .collectionFactory .createConcurrentWeakKeyedMap ();
454+ } else {
455+ NewRelic .getAgent ()
456+ .getLogger ()
457+ .log (Level .INFO ,
458+ "JDBC Statement weak key caching is disabled. Using a ConcurrentHashMap for {0}. All JDBC Statements MUST be closed when this setting is enabled." ,
459+ cacheName );
460+ return AgentBridge .collectionFactory .createVanillaJavaConcurrentHashMap (INITIAL_STATEMENT_CACHE_CAPACITY );
461+ }
462+ }
463+
415464 /**
416465 * Retrieves the cached entity GUID value and initialize the value on first access.
417466 * If the entity GUID is not yet set (empty string), this method will retry on subsequent
0 commit comments