Skip to content

Commit

Permalink
Refactoring: extracted and encapsulates all JDBC Statement caching fu…
Browse files Browse the repository at this point in the history
…nctionality and logic into the StatementCache class.
  • Loading branch information
simeonmalchev committed Oct 19, 2015
1 parent 298776b commit e5e4116
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 251 deletions.
9 changes: 4 additions & 5 deletions src/main/java/org/vibur/dbcp/ViburDBCPConfig.java
Expand Up @@ -19,8 +19,7 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vibur.dbcp.cache.ConnMethodKey;
import org.vibur.dbcp.cache.StatementVal;
import org.vibur.dbcp.cache.StatementCache;
import org.vibur.dbcp.pool.ConnHolder;
import org.vibur.dbcp.pool.PoolOperations;
import org.vibur.objectpool.PoolService;
Expand Down Expand Up @@ -152,7 +151,7 @@ public class ViburDBCPConfig {
* If the statement's cache is not enabled, the client application may safely exclude the dependency
* on ConcurrentLinkedCacheMap from its pom.xml file. */
private int statementCacheMaxSize = 0;
private ConcurrentMap<ConnMethodKey, StatementVal> statementCache = null;
private StatementCache statementCache = null;


/** The list of critical SQL states as a comma separated values, see http://stackoverflow.com/a/14412929/1682918 .
Expand Down Expand Up @@ -419,11 +418,11 @@ public void setStatementCacheMaxSize(int statementCacheMaxSize) {
this.statementCacheMaxSize = statementCacheMaxSize;
}

public ConcurrentMap<ConnMethodKey, StatementVal> getStatementCache() {
public StatementCache getStatementCache() {
return statementCache;
}

public void setStatementCache(ConcurrentMap<ConnMethodKey, StatementVal> statementCache) {
public void setStatementCache(StatementCache statementCache) {
this.statementCache = statementCache;
}

Expand Down
22 changes: 8 additions & 14 deletions src/main/java/org/vibur/dbcp/ViburDBCPDataSource.java
Expand Up @@ -17,9 +17,7 @@
package org.vibur.dbcp;

import org.slf4j.LoggerFactory;
import org.vibur.dbcp.cache.ConnMethodKey;
import org.vibur.dbcp.cache.StatementInvocationCacheProvider;
import org.vibur.dbcp.cache.StatementVal;
import org.vibur.dbcp.cache.StatementCache;
import org.vibur.dbcp.jmx.ViburDBCPMonitoring;
import org.vibur.dbcp.pool.ConnHolder;
import org.vibur.dbcp.pool.ConnectionFactory;
Expand All @@ -42,11 +40,9 @@
import java.sql.SQLFeatureNotSupportedException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

import static org.vibur.dbcp.util.FormattingUtils.getPoolName;
import static org.vibur.dbcp.util.SqlUtils.closeStatement;
import static org.vibur.dbcp.util.ViburUtils.getStackTraceAsString;

/**
Expand Down Expand Up @@ -265,6 +261,10 @@ private void validateConfig() {
logger.warn("Setting logConnectionLongerThanMs to " + getConnectionTimeoutInMs());
setLogConnectionLongerThanMs(getConnectionTimeoutInMs());
}
if (getStatementCacheMaxSize() > CACHE_MAX_SIZE) {
logger.warn("Setting statementCacheMaxSize to " + CACHE_MAX_SIZE);
setStatementCacheMaxSize(CACHE_MAX_SIZE);
}

if (getDefaultTransactionIsolation() != null) {
String defaultTransactionIsolation = getDefaultTransactionIsolation().toUpperCase();
Expand Down Expand Up @@ -300,20 +300,14 @@ private void initPoolReducer() throws ViburDBCPException {

private void initStatementCache() {
int statementCacheMaxSize = getStatementCacheMaxSize();
if (statementCacheMaxSize > CACHE_MAX_SIZE)
statementCacheMaxSize = CACHE_MAX_SIZE;
if (statementCacheMaxSize > 0)
setStatementCache(new StatementInvocationCacheProvider(statementCacheMaxSize).build());
setStatementCache(new StatementCache(statementCacheMaxSize));
}

private void terminateStatementCache() {
ConcurrentMap<ConnMethodKey, StatementVal> statementCache = getStatementCache();
StatementCache statementCache = getStatementCache();
if (statementCache != null) {
for (Map.Entry<ConnMethodKey, StatementVal> entry : statementCache.entrySet()) {
StatementVal value = entry.getValue();
statementCache.remove(entry.getKey(), value);
closeStatement(value.value());
}
statementCache.clear();
setStatementCache(null);
}
}
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion src/main/java/org/vibur/dbcp/cache/ConnMethodKey.java
Expand Up @@ -26,7 +26,6 @@
* <p>Used as a caching {@code key} for method invocations in a {@link java.util.concurrent.ConcurrentMap}
* cache implementation.
*
* @see StatementInvocationCacheProvider
* @see StatementVal
*
* @author Simeon Malchev
Expand Down
143 changes: 143 additions & 0 deletions src/main/java/org/vibur/dbcp/cache/StatementCache.java
@@ -0,0 +1,143 @@
/**
* Copyright 2015 Simeon Malchev
*
* 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.
*/

package org.vibur.dbcp.cache;

import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;
import org.slf4j.LoggerFactory;
import org.vibur.dbcp.proxy.TargetInvoker;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import static org.vibur.dbcp.cache.StatementVal.*;
import static org.vibur.dbcp.util.SqlUtils.clearWarnings;
import static org.vibur.dbcp.util.SqlUtils.closeStatement;

/**
* Implements and encapsulates all JDBC Statement caching functionality and logic.
*
* @author Simeon Malchev
*/
public class StatementCache {

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(StatementCache.class);

private final ConcurrentMap<ConnMethodKey, StatementVal> statementCache;

public StatementCache(int maxSize) {
if (maxSize <= 0)
throw new IllegalArgumentException();
statementCache = buildStatementCache(maxSize);
}

protected ConcurrentMap<ConnMethodKey, StatementVal> buildStatementCache(int maxSize) {
return new ConcurrentLinkedHashMap.Builder<ConnMethodKey, StatementVal>()
.initialCapacity(maxSize)
.maximumWeightedCapacity(maxSize)
.listener(getListener())
.build();
}

protected EvictionListener<ConnMethodKey, StatementVal> getListener() {
return new EvictionListener<ConnMethodKey, StatementVal>() {
public void onEviction(ConnMethodKey key, StatementVal value) {
if (value.state().getAndSet(EVICTED) == AVAILABLE)
closeStatement(value.value());
logger.trace("Evicted {}", value.value());
}
};
}

/**
* Returns <i>a possible</i> cached StatementVal object for the given connection method key.
*
* @param key the connection method key
* @param targetInvoker the targetInvoker via which to create the raw JDBC Statement object, if needed
* @return a retrieved from the cache or newly created StatementVal holder object wrapping the raw JDBC Statement object
* @throws Throwable if the invoked underlying prepareXYZ method throws an exception
*/
public StatementVal retrieve(ConnMethodKey key, TargetInvoker targetInvoker) throws Throwable {
StatementVal statement = statementCache.get(key);
if (statement == null || !statement.state().compareAndSet(AVAILABLE, IN_USE)) {
Statement rawStatement = (Statement) targetInvoker.targetInvoke(key.getMethod(), key.getArgs());
if (statement == null) { // there was no entry for the key, so we'll try to put a new one
statement = new StatementVal(rawStatement, new AtomicInteger(IN_USE));
if (statementCache.putIfAbsent(key, statement) == null)
return statement; // the new entry was successfully put in the cache
}
return new StatementVal(rawStatement, null);
} else { // the statement was in the cache and was available
if (logger.isTraceEnabled())
logger.trace("Using cached statement for {}", toString(key));
return statement;
}
}

public void restore(StatementVal statement, boolean clear) {
Statement rawStatement = statement.value();
if (statement.state() != null) { // if this statement is in the cache
if (clear)
clearWarnings(rawStatement);
if (!statement.state().compareAndSet(IN_USE, AVAILABLE)) // just mark it as available if its state was in_use
closeStatement(rawStatement); // and close it if it was already evicted (while its state was in_use)
} else
closeStatement(rawStatement);
}

public boolean remove(Statement rawStatement, boolean close) {
for (Map.Entry<ConnMethodKey, StatementVal> entry : statementCache.entrySet()) {
StatementVal value = entry.getValue();
if (value.value() == rawStatement) { // comparing with == as these JDBC Statements are cached objects
if (close)
closeStatement(rawStatement);
return statementCache.remove(entry.getKey(), value);
}
}
return false;
}

public int removeAll(Connection rawConnection) {
int removed = 0;
for (Map.Entry<ConnMethodKey, StatementVal> entry : statementCache.entrySet()) {
ConnMethodKey key = entry.getKey();
StatementVal value = entry.getValue();
if (key.getTarget() == rawConnection && statementCache.remove(key, value)) {
closeStatement(value.value());
removed++;
}
}
return removed;
}

public void clear() {
for (Map.Entry<ConnMethodKey, StatementVal> entry : statementCache.entrySet()) {
StatementVal value = entry.getValue();
statementCache.remove(entry.getKey(), value);
closeStatement(value.value());
}
}

public String toString(ConnMethodKey key) {
return String.format("connection %s, method %s, args %s",
key.getTarget(), key.getMethod(), Arrays.toString(key.getArgs()));
}
}

This file was deleted.

1 change: 0 additions & 1 deletion src/main/java/org/vibur/dbcp/cache/StatementVal.java
Expand Up @@ -25,7 +25,6 @@
* implementation) for the invocations of {@code Connection.prepareStatement} and {@code Connection.prepareCall}
* methods, and their "state" is describing whether the object is currently available, in_use, or evicted.
*
* @see StatementInvocationCacheProvider
* @see ConnMethodKey
*
* @author Simeon Malchev
Expand Down
18 changes: 4 additions & 14 deletions src/main/java/org/vibur/dbcp/pool/ConnectionFactory.java
Expand Up @@ -21,16 +21,13 @@
import org.slf4j.LoggerFactory;
import org.vibur.dbcp.ViburDBCPConfig;
import org.vibur.dbcp.ViburDBCPException;
import org.vibur.dbcp.cache.ConnMethodKey;
import org.vibur.dbcp.cache.StatementVal;
import org.vibur.dbcp.cache.StatementCache;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

Expand Down Expand Up @@ -228,16 +225,9 @@ public void destroy(ConnHolder conn) {
}

private void closeStatements(Connection connection) {
ConcurrentMap<ConnMethodKey, StatementVal> statementCache = config.getStatementCache();
if (statementCache == null)
return;

for (Map.Entry<ConnMethodKey, StatementVal> entry : statementCache.entrySet()) {
ConnMethodKey key = entry.getKey();
StatementVal value = entry.getValue();
if (key.getTarget() == connection && statementCache.remove(key, value))
closeStatement(value.value());
}
StatementCache statementCache = config.getStatementCache();
if (statementCache != null)
statementCache.removeAll(connection);
}

/** {@inheritDoc} */
Expand Down

0 comments on commit e5e4116

Please sign in to comment.