Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement prepared statement caching (alternative approach) #69

Merged
merged 9 commits into from Feb 24, 2014
53 changes: 43 additions & 10 deletions src/main/java/com/impossibl/postgres/jdbc/AbstractDataSource.java
Expand Up @@ -37,6 +37,8 @@
import java.util.Properties;
import java.util.logging.Logger;

import static java.lang.Boolean.parseBoolean;

import javax.sql.CommonDataSource;

/**
Expand All @@ -52,7 +54,8 @@ public abstract class AbstractDataSource implements CommonDataSource {
private String user;
private String password;
private boolean housekeeper;
private int parsedSqlCache;
private int parsedSqlCacheSize;
private int preparedStatementCacheSize;

/**
* Constructor
Expand All @@ -64,27 +67,31 @@ protected AbstractDataSource() {
this.database = null;
this.user = null;
this.password = null;
this.housekeeper = true;
this.parsedSqlCache = 250;
this.housekeeper = parseBoolean(PGSettings.HOUSEKEEPER_ENABLED_DEFAULT_DATASOURCE);
this.parsedSqlCacheSize = Settings.PARSED_SQL_CACHE_SIZE_DEFAULT;
this.preparedStatementCacheSize = Settings.PREPARED_STATEMENT_CACHE_SIZE_DEFAULT;
}

/**
* {@inheritDoc}
*/
@Override
public int getLoginTimeout() throws SQLException {
return loginTimeout;
}

/**
* {@inheritDoc}
*/
@Override
public void setLoginTimeout(int seconds) throws SQLException {
loginTimeout = seconds;
}

/**
* {@inheritDoc}
*/
@Override
public PrintWriter getLogWriter() throws SQLException {
// Not supported
return null;
Expand All @@ -93,13 +100,15 @@ public PrintWriter getLogWriter() throws SQLException {
/**
* {@inheritDoc}
*/
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// Not supported
}

/**
* {@inheritDoc}
*/
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return Logger.getLogger(Context.class.getPackage().getName());
}
Expand Down Expand Up @@ -205,7 +214,7 @@ public void setHousekeeper(boolean v) {
* @return the number of SQL statements' parsed structures allowed in the cache
*/
public int getParsedSqlCacheSize() {
return parsedSqlCache;
return parsedSqlCacheSize;
}

/**
Expand All @@ -215,15 +224,38 @@ public int getParsedSqlCacheSize() {
* @param cacheSize the number of SQL statements' parsed structures to cache
*/
public void setParsedSqlCacheSize(int cacheSize) {
parsedSqlCache = cacheSize;
parsedSqlCacheSize = cacheSize;
}

/**
* Get the size of the prepared statement cache
*
* @return the maximum number of PreparedStatements cached per connection
*/
public int getPreparedStatementCacheSize() {
return preparedStatementCacheSize;
}

/**
* Set the size of the preapred statement cache
*
* @param preparedStatementCacheSize
* the maximum number of PreparedStatements cached per connection
*/
public void setPreparedStatementCacheSize(int preparedStatementCacheSize) {
this.preparedStatementCacheSize = preparedStatementCacheSize;
}

/**
* Create a connection
* @param u The user name
* @param p The password
*
* @param u
* The user name
* @param p
* The password
* @return The connection
* @exception SQLException Thrown in case of an error
* @exception SQLException
* Thrown in case of an error
*/
protected PGConnectionImpl createConnection(String u, String p) throws SQLException {
String url = buildUrl();
Expand Down Expand Up @@ -251,9 +283,10 @@ else if (password != null) {

Housekeeper hk = null;
if (housekeeper)
hk = new ThreadedHousekeeper();
hk = ThreadedHousekeeper.instance;

props.put(Settings.PARSED_SQL_CACHE, parsedSqlCache);
props.put(Settings.PARSED_SQL_CACHE_SIZE, parsedSqlCacheSize);
props.put(Settings.PREPARED_STATEMENT_CACHE_SIZE, preparedStatementCacheSize);

return ConnectionUtil.createConnection(url, props, hk);
}
Expand Down
112 changes: 110 additions & 2 deletions src/main/java/com/impossibl/postgres/jdbc/PGConnectionImpl.java
Expand Up @@ -37,6 +37,8 @@
import com.impossibl.postgres.protocol.Command;
import com.impossibl.postgres.protocol.Protocol;
import com.impossibl.postgres.protocol.QueryCommand;
import com.impossibl.postgres.protocol.ResultField;
import com.impossibl.postgres.protocol.ServerObjectType;
import com.impossibl.postgres.system.BasicContext;
import com.impossibl.postgres.system.NoticeException;
import com.impossibl.postgres.types.ArrayType;
Expand Down Expand Up @@ -68,7 +70,10 @@
import static com.impossibl.postgres.jdbc.SQLTextUtils.prependCursorDeclaration;
import static com.impossibl.postgres.protocol.TransactionStatus.Idle;
import static com.impossibl.postgres.system.Settings.CONNECTION_READONLY;
import static com.impossibl.postgres.system.Settings.PARSED_SQL_CACHE;
import static com.impossibl.postgres.system.Settings.PARSED_SQL_CACHE_SIZE;
import static com.impossibl.postgres.system.Settings.PARSED_SQL_CACHE_SIZE_DEFAULT;
import static com.impossibl.postgres.system.Settings.PREPARED_STATEMENT_CACHE_SIZE;
import static com.impossibl.postgres.system.Settings.PREPARED_STATEMENT_CACHE_SIZE_DEFAULT;

import java.io.IOException;
import java.io.InterruptedIOException;
Expand Down Expand Up @@ -98,6 +103,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;

import static java.lang.Boolean.parseBoolean;
Expand All @@ -109,6 +115,8 @@
import static java.util.Collections.unmodifiableMap;
import static java.util.concurrent.TimeUnit.SECONDS;



/**
* Connection implementation
* @author <a href="mailto:kdubb@me.com">Kevin Wooten</a>
Expand Down Expand Up @@ -163,6 +171,7 @@ public void run() {
int networkTimeout;
SQLWarning warningChain;
List<WeakReference<PGStatement>> activeStatements;
Map<CachedStatementKey, CachedStatement> preparedStatementCache;
final Housekeeper housekeeper;
final Object cleanupKey;

Expand All @@ -173,12 +182,35 @@ public void run() {

this.activeStatements = new ArrayList<>();

final int sqlCacheSize = getSetting(PARSED_SQL_CACHE, 250);
final int statementCacheSize = getSetting(PREPARED_STATEMENT_CACHE_SIZE, PREPARED_STATEMENT_CACHE_SIZE_DEFAULT);
if (statementCacheSize > 0) {
preparedStatementCache = Collections.synchronizedMap(new LinkedHashMap<CachedStatementKey, CachedStatement>(statementCacheSize + 1, 1.1f, true) {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<CachedStatementKey, CachedStatement> eldest) {
if (size() > statementCacheSize) {
try {
PGStatement.dispose(PGConnectionImpl.this, ServerObjectType.Statement, eldest.getValue().name);
}
catch (SQLException e) {
// Ignore...
}
return true;
}
else {
return false;
}
}
});
}

final int sqlCacheSize = getSetting(PARSED_SQL_CACHE_SIZE, PARSED_SQL_CACHE_SIZE_DEFAULT);
if (sqlCacheSize > 0) {
synchronized (PGConnectionImpl.class) {
if (parsedSqlCache == null) {
parsedSqlCache = Collections.synchronizedMap(new LinkedHashMap<String, SQLText>(sqlCacheSize + 1, 1.1f, true) {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<String, SQLText> eldest) {
return size() > sqlCacheSize;
}
Expand Down Expand Up @@ -1213,4 +1245,80 @@ public void removeNotificationListener(PGNotificationListener listener) {
super.removeNotificationListener(listener);
}

CachedStatement getCachedStatement(CachedStatementKey key, Callable<CachedStatement> loader) throws Exception {

if (preparedStatementCache == null) {
return loader.call();
}

CachedStatement cached = preparedStatementCache.get(key);
if (cached == null) {

cached = loader.call();

preparedStatementCache.put(key, cached);
}

return cached;
}

}

class CachedStatementKey {

String sql;
List<Type> parameterTypes;

public CachedStatementKey(String sql, List<Type> parameterTypes) {
this.sql = sql;
this.parameterTypes = parameterTypes;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((parameterTypes == null) ? 0 : parameterTypes.hashCode());
result = prime * result + ((sql == null) ? 0 : sql.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CachedStatementKey other = (CachedStatementKey) obj;
if (parameterTypes == null) {
if (other.parameterTypes != null)
return false;
}
else if (!parameterTypes.equals(other.parameterTypes))
return false;
if (sql == null) {
if (other.sql != null)
return false;
}
else if (!sql.equals(other.sql))
return false;
return true;
}

}

class CachedStatement {

String name;
List<Type> parameterTypes;
List<ResultField> resultFields;

public CachedStatement(String statementName, List<Type> parameterTypes, List<ResultField> resultFields) {
this.name = statementName;
this.parameterTypes = parameterTypes;
this.resultFields = resultFields;
}

}
5 changes: 1 addition & 4 deletions src/main/java/com/impossibl/postgres/jdbc/PGDriver.java
Expand Up @@ -51,16 +51,13 @@ public class PGDriver implements Driver {
/** The version of the driver */
public static final Version VERSION = Version.get(0, 1, 0);

/** The housekeeper */
private ThreadedHousekeeper realHousekeeper = new ThreadedHousekeeper();

public PGDriver() throws SQLException {
DriverManager.registerDriver(this);
}

@Override
public PGConnection connect(String url, Properties info) throws SQLException {
return ConnectionUtil.createConnection(url, info, realHousekeeper);
return ConnectionUtil.createConnection(url, info, ThreadedHousekeeper.instance);
}

@Override
Expand Down