diff --git a/CHANGES b/CHANGES
index c41175a8..d8cc6725 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,26 @@
HikariCP Changes
+Changes between 1.2.6 and 1.2.7
+
+ *) Finally achieved performance parity between the generated delegates
+ and the former method of instrumenting driver classes directly.
+
+ *) Improved generated delegate code. Removed unnecessary casts, moved
+ to a static proxy factory rather than a singleton (performance win).
+
+ *) Improved performance of FastStatementList (primary source of speed-up
+ to reach parity with former instrumentation code).
+
+ *) Removed aliveness check on connection creation.
+
+ *) Track connection isolation level and only reset if the state has
+ become "dirty". Avoids unnecessary round trip to the DB during the
+ aliveness check.
+
+ *) Added interface IConnectionCustomizer and related HikariConfig
+ property 'connectionCustomizerClassName' to allow users to specify
+ a connection customization implementation.
+
Changes between 1.2.5 and 1.2.6
*) Fixed regression that caused IndexOutOfBounds when multiple unclosed
@@ -17,7 +38,7 @@ Changes between 1.2.4 and 1.2.5
HikariConfig programmatically or constructing HikariConfig from a
java.util.Properties instance.
- *) Fixed Hibernate threading issue (see in use usage patterns) introduced
+ *) Fixed Hibernate threading issue (certain usage patterns) introduced
in 1.2.2.
*) Fixed issue observed with PostgreSQL whereby the query that tests the
diff --git a/benchmark.sh b/benchmark.sh
index ccf49aa8..20c4085a 100755
--- a/benchmark.sh
+++ b/benchmark.sh
@@ -11,7 +11,7 @@ CLASSPATH=$CLASSPATH:~/.m2/repository/org/apache/tomcat/tomcat-jdbc/7.0.47/tomca
CLASSPATH=$CLASSPATH:~/.m2/repository/org/apache/tomcat/tomcat-juli/7.0.47/tomcat-juli-7.0.47.jar
CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/tools.jar
-CLASSPATH=$CLASSPATH:./target/HikariCP-1.2.6-SNAPSHOT.jar
+CLASSPATH=$CLASSPATH:./target/HikariCP-1.2.7-SNAPSHOT.jar
CLASSPATH=$CLASSPATH:./target/test-classes
java -classpath $CLASSPATH \
diff --git a/pom.xml b/pom.xml
index 582f3502..1528efc3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,13 +58,12 @@
org.slf4j
slf4j-api
1.7.5
- compile
org.slf4j
slf4j-simple
1.7.5
- test
+ true
org.mockito
@@ -90,17 +89,11 @@
2.3.0
test
-
- com.sun
- tools
- 1.6.0
- system
- ${java.home}/../lib/tools.jar
-
org.javassist
javassist
3.18.1-GA
+ compile
net.snaq
diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java
index 97310a36..f4e546c2 100644
--- a/src/main/java/com/zaxxer/hikari/HikariConfig.java
+++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java
@@ -29,6 +29,7 @@ import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.zaxxer.hikari.proxy.JavassistProxyFactory;
import com.zaxxer.hikari.util.PropertyBeanSetter;
public final class HikariConfig implements HikariConfigMBean
@@ -49,20 +50,26 @@ public final class HikariConfig implements HikariConfigMBean
private volatile long idleTimeout;
private volatile long leakDetectionThreshold;
private volatile long maxLifetime;
- private volatile int minPoolSize;
private volatile int maxPoolSize;
+ private volatile int minPoolSize;
// Properties NOT changeable at runtime
//
private int transactionIsolation;
- private String poolName;
+ private String connectionCustomizerClassName;
+ private String connectionInitSql;
private String connectionTestQuery;
private String dataSourceClassName;
- private String connectionInitSql;
- private boolean isJdbc4connectionTest;
+ private String poolName;
private boolean isAutoCommit;
- private Properties dataSourceProperties;
+ private boolean isJdbc4connectionTest;
private DataSource dataSource;
+ private Properties dataSourceProperties;
+
+ static
+ {
+ JavassistProxyFactory.initialize();
+ }
/**
* Default constructor
@@ -172,6 +179,16 @@ public final class HikariConfig implements HikariConfigMBean
this.acquireRetryDelay = acquireRetryDelayMs;
}
+ public String getConnectionCustomizerClassName()
+ {
+ return connectionCustomizerClassName;
+ }
+
+ public void setConnectionCustomizerClassName(String connectionCustomizerClassName)
+ {
+ this.connectionCustomizerClassName = connectionCustomizerClassName;
+ }
+
public String getConnectionTestQuery()
{
return connectionTestQuery;
@@ -401,6 +418,19 @@ public final class HikariConfig implements HikariConfigMBean
acquireRetryDelay = ACQUIRE_RETRY_DELAY;
}
+ if (connectionCustomizerClassName != null)
+ {
+ try
+ {
+ getClass().getClassLoader().loadClass(connectionCustomizerClassName);
+ }
+ catch (ClassNotFoundException e)
+ {
+ logger.warn("connectionCustomizationClass specified class '" + connectionCustomizerClassName + "' could not be loaded", e);
+ connectionCustomizerClassName = null;
+ }
+ }
+
if (connectionTimeout == Integer.MAX_VALUE)
{
logger.warn("No connection wait timeout is set, this might cause an infinite wait.");
diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java
index 966da0be..20d24f4e 100644
--- a/src/main/java/com/zaxxer/hikari/HikariPool.java
+++ b/src/main/java/com/zaxxer/hikari/HikariPool.java
@@ -32,7 +32,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.proxy.IHikariConnectionProxy;
-import com.zaxxer.hikari.proxy.JavassistProxyFactoryFactory;
+import com.zaxxer.hikari.proxy.ProxyFactory;
import com.zaxxer.hikari.util.PropertyBeanSetter;
/**
@@ -47,20 +47,20 @@ public final class HikariPool implements HikariPoolMBean
final DataSource dataSource;
+ private final IConnectionCustomizer connectionCustomizer;
private final HikariConfig configuration;
private final LinkedTransferQueue idleConnections;
+ private final Timer houseKeepingTimer;
- private final AtomicInteger totalConnections;
- private final AtomicInteger idleConnectionCount;
- private final AtomicBoolean backgroundFillQueued;
private final long leakDetectionThreshold;
- private final boolean jdbc4ConnectionTest;
+ private final AtomicBoolean backgroundFillQueued;
+ private final AtomicInteger idleConnectionCount;
+ private final AtomicInteger totalConnections;
private final boolean isAutoCommit;
+ private final boolean jdbc4ConnectionTest;
private int transactionIsolation;
private boolean debug;
- private final Timer houseKeepingTimer;
-
/**
* Construct a HikariPool with the specified configuration.
*
@@ -101,6 +101,23 @@ public final class HikariPool implements HikariPoolMBean
this.dataSource = configuration.getDataSource();
}
+ if (configuration.getConnectionCustomizerClassName() != null)
+ {
+ try
+ {
+ Class> clazz = this.getClass().getClassLoader().loadClass(configuration.getConnectionCustomizerClassName());
+ this.connectionCustomizer = (IConnectionCustomizer) clazz.newInstance();
+ }
+ catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
+ {
+ throw new RuntimeException("Could not load connection customization class", e);
+ }
+ }
+ else
+ {
+ this.connectionCustomizer = null;
+ }
+
HikariMBeanElf.registerMBeans(configuration, this);
houseKeepingTimer = new Timer("Hikari Housekeeping Timer", true);
@@ -325,27 +342,26 @@ public final class HikariPool implements HikariPoolMBean
try
{
Connection connection = dataSource.getConnection();
- IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) JavassistProxyFactoryFactory.getProxyFactory().getProxyConnection(this, connection);
-
+
if (transactionIsolation < 0)
{
transactionIsolation = connection.getTransactionIsolation();
}
-
- boolean alive = isConnectionAlive(proxyConnection, configuration.getConnectionTimeout());
- if (!alive)
+
+ if (connectionCustomizer != null)
{
- // This will be caught below...
- throw new RuntimeException("Connection not alive, retry.");
+ connectionCustomizer.customize(connection);
}
+ IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) ProxyFactory.getProxyConnection(this, connection, transactionIsolation);
+
String initSql = configuration.getConnectionInitSql();
if (initSql != null && initSql.length() > 0)
{
connection.setAutoCommit(true);
try (Statement statement = connection.createStatement())
{
- statement.executeQuery(initSql);
+ statement.execute(initSql);
}
}
@@ -388,20 +404,29 @@ public final class HikariPool implements HikariPoolMBean
* @param timeoutMs the timeout before we consider the test a failure
* @return true if the connection is alive, false if it is not alive or we timed out
*/
- private boolean isConnectionAlive(final Connection connection, long timeoutMs)
+ private boolean isConnectionAlive(final IHikariConnectionProxy connection, long timeoutMs)
{
- if (timeoutMs < 1000)
- {
- timeoutMs = 1000;
- }
-
try
{
connection.setAutoCommit(isAutoCommit);
- connection.setTransactionIsolation(transactionIsolation);
+ if (connection.isTransactionIsolationDirty())
+ {
+ connection.setTransactionIsolation(transactionIsolation);
+ }
+ // If the connection was used less than a second ago, short-circuit the alive test
+ if (System.currentTimeMillis() - connection.getLastAccess() < 1000)
+ {
+ return true;
+ }
+
try
{
+ if (timeoutMs < 1000)
+ {
+ timeoutMs = 1000;
+ }
+
if (jdbc4ConnectionTest)
{
return connection.isValid((int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs));
@@ -409,6 +434,7 @@ public final class HikariPool implements HikariPoolMBean
try (Statement statement = connection.createStatement())
{
+ statement.setQueryTimeout((int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs));
statement.executeQuery(configuration.getConnectionTestQuery());
}
}
@@ -418,13 +444,15 @@ public final class HikariPool implements HikariPoolMBean
{
connection.commit();
}
+
+ connection.resetTransactionIsolationDirty();
}
return true;
}
catch (SQLException e)
{
- LOGGER.error("Exception during keep alive check. Connection must be dead.", e);
+ LOGGER.warn("Exception during keep alive check, that means the connection must be dead.", e);
return false;
}
}
diff --git a/src/main/java/com/zaxxer/hikari/IConnectionCustomizer.java b/src/main/java/com/zaxxer/hikari/IConnectionCustomizer.java
new file mode 100644
index 00000000..a0b4390c
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/IConnectionCustomizer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * 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 com.zaxxer.hikari;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * Interface whose implementers can perform one-time customization of a
+ * Connection before it is added to the pool. Note the implemention
+ * of the customize()
method must be multithread-safe as
+ * it may be called by multiple threads at one time.
+ *
+ * @author Brett Wooldridge
+ */
+public interface IConnectionCustomizer
+{
+ /**
+ * The Connection object that is passed into this method is the "raw"
+ * Connection instance provided by the JDBC driver, not a wrapped
+ * HikariCP connection.
+ *
+ * @param connection a native JDBC driver Connection instance to customize
+ * @throws SQLException should be thrown if an error condition is encountered during customization
+ */
+ void customize(Connection connection) throws SQLException;
+}
diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java
index c4e14a24..391db997 100644
--- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java
+++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java
@@ -21,13 +21,13 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import com.zaxxer.hikari.HikariPool;
+import com.zaxxer.hikari.util.FastStatementList;
/**
* This is the proxy class for java.sql.Connection.
@@ -36,17 +36,17 @@ import com.zaxxer.hikari.HikariPool;
*/
public abstract class ConnectionProxy implements IHikariConnectionProxy
{
- private static final ProxyFactory PROXY_FACTORY;
-
private static final Set SQL_ERRORS;
protected final Connection delegate;
- private final ArrayList openStatements;
+ private final FastStatementList openStatements;
private final HikariPool parentPool;
+ private final int defaultIsolationLevel;
private boolean isClosed;
private boolean forceClose;
+ private boolean isTransactionIsolationDirty;
private final long creationTime;
private volatile long lastAccess;
@@ -63,17 +63,16 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
SQL_ERRORS.add("57P03"); // CANNOT CONNECT NOW
SQL_ERRORS.add("57P02"); // CRASH SHUTDOWN
SQL_ERRORS.add("01002"); // SQL92 disconnect error
-
- PROXY_FACTORY = JavassistProxyFactoryFactory.getProxyFactory();
}
- protected ConnectionProxy(HikariPool pool, Connection connection)
+ protected ConnectionProxy(HikariPool pool, Connection connection, int defaultIsolationLevel)
{
this.parentPool = pool;
this.delegate = connection;
+ this.defaultIsolationLevel = defaultIsolationLevel;
creationTime = lastAccess = System.currentTimeMillis();
- openStatements = new ArrayList();
+ openStatements = new FastStatementList();
}
public final void unregisterStatement(Object statement)
@@ -117,6 +116,16 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
scheduler.schedule(leakTask, leakDetectionThreshold);
}
+ public final boolean isTransactionIsolationDirty()
+ {
+ return isTransactionIsolationDirty;
+ }
+
+ public void resetTransactionIsolationDirty()
+ {
+ isTransactionIsolationDirty = false;
+ }
+
public final boolean isBrokenConnection()
{
return forceClose;
@@ -403,67 +412,102 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
}
}
+ /** {@inheritDoc} */
+ public void setTransactionIsolation(int level) throws SQLException
+ {
+ checkClosed();
+ try
+ {
+ delegate.setTransactionIsolation(level);
+ isTransactionIsolationDirty = (level != defaultIsolationLevel);
+ }
+ catch (SQLException e)
+ {
+ checkException(e);
+ throw e;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean isWrapperFor(Class> iface) throws SQLException
+ {
+ return iface.isInstance(delegate);
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ public T unwrap(Class iface) throws SQLException
+ {
+ if (iface.isInstance(delegate))
+ {
+ return (T) delegate;
+ }
+
+ throw new SQLException("Wrapped connection is not an instance of " + iface);
+ }
+
+
// **********************************************************************
// Private Methods
// **********************************************************************
private final Statement __createStatement() throws SQLException
{
- return PROXY_FACTORY.getProxyStatement(this, delegate.createStatement());
+ return ProxyFactory.getProxyStatement(this, delegate.createStatement());
}
private final Statement __createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
{
- return PROXY_FACTORY.getProxyStatement(this, delegate.createStatement(resultSetType, resultSetConcurrency));
+ return ProxyFactory.getProxyStatement(this, delegate.createStatement(resultSetType, resultSetConcurrency));
}
private final Statement __createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
- return PROXY_FACTORY.getProxyStatement(this, delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
+ return ProxyFactory.getProxyStatement(this, delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
}
private final CallableStatement __prepareCall(String sql) throws SQLException
{
- return PROXY_FACTORY.getProxyCallableStatement(this, delegate.prepareCall(sql));
+ return ProxyFactory.getProxyCallableStatement(this, delegate.prepareCall(sql));
}
private final CallableStatement __prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
{
- return PROXY_FACTORY.getProxyCallableStatement(this, delegate.prepareCall(sql, resultSetType, resultSetConcurrency));
+ return ProxyFactory.getProxyCallableStatement(this, delegate.prepareCall(sql, resultSetType, resultSetConcurrency));
}
private final CallableStatement __prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
- return PROXY_FACTORY.getProxyCallableStatement(this, delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
+ return ProxyFactory.getProxyCallableStatement(this, delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
}
private final PreparedStatement __prepareStatement(String sql) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql));
}
private final PreparedStatement __prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, autoGeneratedKeys));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, autoGeneratedKeys));
}
private final PreparedStatement __prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, resultSetType, resultSetConcurrency));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, resultSetType, resultSetConcurrency));
}
private final PreparedStatement __prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
}
private final PreparedStatement __prepareStatement(String sql, int[] columnIndexes) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnIndexes));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnIndexes));
}
private final PreparedStatement __prepareStatement(String sql, String[] columnNames) throws SQLException
{
- return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
+ return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}
}
diff --git a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java
index 8f15e0e6..44277a74 100644
--- a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java
+++ b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java
@@ -36,6 +36,10 @@ public interface IHikariConnectionProxy extends Connection
boolean isBrokenConnection();
+ boolean isTransactionIsolationDirty();
+
+ void resetTransactionIsolationDirty();
+
long getCreationTime();
long getLastAccess();
diff --git a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactoryFactory.java b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java
similarity index 71%
rename from src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactoryFactory.java
rename to src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java
index ac6ae306..5598a551 100644
--- a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactoryFactory.java
+++ b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java
@@ -30,6 +30,9 @@ import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
+import javassist.NotFoundException;
+
+import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.util.ClassLoaderUtils;
@@ -37,10 +40,8 @@ import com.zaxxer.hikari.util.ClassLoaderUtils;
*
* @author Brett Wooldridge
*/
-public final class JavassistProxyFactoryFactory
+public final class JavassistProxyFactory
{
- private static final ProxyFactory proxyFactory;
-
private ClassPool classPool;
static
@@ -48,11 +49,10 @@ public final class JavassistProxyFactoryFactory
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try
{
- Thread.currentThread().setContextClassLoader(JavassistProxyFactoryFactory.class.getClassLoader());
+ Thread.currentThread().setContextClassLoader(JavassistProxyFactory.class.getClassLoader());
- JavassistProxyFactoryFactory proxyFactoryFactory = new JavassistProxyFactoryFactory();
-
- proxyFactory = proxyFactoryFactory.generateProxyFactory();
+ JavassistProxyFactory proxyFactoryFactory = new JavassistProxyFactory();
+ proxyFactoryFactory.modifyProxyFactory();
}
catch (Exception e)
{
@@ -64,7 +64,12 @@ public final class JavassistProxyFactoryFactory
}
}
- private JavassistProxyFactoryFactory()
+ public static void initialize()
+ {
+ // simply invoking this method causes the initialization of this class.
+ }
+
+ private JavassistProxyFactory()
{
classPool = new ClassPool();
classPool.importPackage("java.sql");
@@ -73,15 +78,18 @@ public final class JavassistProxyFactoryFactory
try
{
// Connection is special, it has a checkClosed() call at the beginning
- String methodBody = "{ checkClosed(); try { return ((cast) delegate).method($$); } catch (SQLException e) { checkException(e); throw e;} }";
+ String methodBody = "{ checkClosed(); try { return delegate.method($$); } catch (SQLException e) { checkException(e); throw e;} }";
generateProxyClass(Connection.class, ConnectionProxy.class, methodBody);
- // The result of the proxy classes simply delegate
- methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { checkException(e); throw e;} }";
+ // Cast is not needed for these
+ methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { checkException(e); throw e;} }";
generateProxyClass(Statement.class, StatementProxy.class, methodBody);
- generateProxyClass(CallableStatement.class, CallableStatementProxy.class, methodBody);
- generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class, methodBody);
generateProxyClass(ResultSet.class, ResultSetProxy.class, methodBody);
+
+ // For these we have to cast the delegate
+ methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { checkException(e); throw e;} }";
+ generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class, methodBody);
+ generateProxyClass(CallableStatement.class, CallableStatementProxy.class, methodBody);
}
catch (Exception e)
{
@@ -89,51 +97,43 @@ public final class JavassistProxyFactoryFactory
}
}
- public static ProxyFactory getProxyFactory()
+ private void modifyProxyFactory() throws Exception
{
- return proxyFactory;
- }
-
- private ProxyFactory generateProxyFactory() throws Exception
- {
- String packageName = ProxyFactory.class.getPackage().getName();
- CtClass targetCt = classPool.makeClass(packageName + ".JavassistProxyFactory");
- CtClass superCt = classPool.getCtClass(ProxyFactory.class.getName());
- targetCt.setSuperclass(superCt);
- targetCt.setModifiers(Modifier.FINAL);
-
- for (CtMethod intfMethod : superCt.getDeclaredMethods())
+ String packageName = JavassistProxyFactory.class.getPackage().getName();
+ CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.proxy.ProxyFactory");
+ for (CtMethod method : proxyCt.getMethods())
{
- CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);
-
StringBuilder call = new StringBuilder("{");
if ("getProxyConnection".equals(method.getName()))
{
call.append("return new ").append(packageName).append(".ConnectionJavassistProxy($$);");
}
- if ("getProxyStatement".equals(method.getName()))
+ else if ("getProxyStatement".equals(method.getName()))
{
call.append("return new ").append(packageName).append(".StatementJavassistProxy($$);");
}
- if ("getProxyPreparedStatement".equals(method.getName()))
+ else if ("getProxyPreparedStatement".equals(method.getName()))
{
call.append("return new ").append(packageName).append(".PreparedStatementJavassistProxy($$);");
}
- if ("getProxyResultSet".equals(method.getName()))
+ else if ("getProxyResultSet".equals(method.getName()))
{
call.append("return $2 != null ? new ").append(packageName).append(".ResultSetJavassistProxy($$) : null;");
}
- if ("getProxyCallableStatement".equals(method.getName()))
+ else if ("getProxyCallableStatement".equals(method.getName()))
{
call.append("return new ").append(packageName).append(".CallableStatementJavassistProxy($$);");
}
+ else
+ {
+ continue;
+ }
+
call.append('}');
method.setBody(call.toString());
- targetCt.addMethod(method);
}
- Class> clazz = targetCt.toClass(classPool.getClassLoader(), null);
- return (ProxyFactory) clazz.newInstance();
+ proxyCt.toClass(classPool.getClassLoader(), null);
}
/**
@@ -187,7 +187,16 @@ public final class JavassistProxyFactoryFactory
CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);
// Generate a method that simply invokes the same method on the delegate
- String modifiedBody = methodBody.replace("method", method.getName());
+ String modifiedBody;
+ if (isThrowsSqlException(intfMethod))
+ {
+ modifiedBody = methodBody.replace("method", method.getName());
+ }
+ else
+ {
+ modifiedBody = "return ((cast) delegate).method($$);".replace("method", method.getName()).replace("cast", primaryInterface.getName());
+ }
+
if (method.getReturnType() == CtClass.voidType)
{
modifiedBody = modifiedBody.replace("return", "");
@@ -198,6 +207,31 @@ public final class JavassistProxyFactoryFactory
}
}
+ if (LoggerFactory.getLogger(getClass()).isDebugEnabled())
+ {
+ targetCt.debugWriteFile(System.getProperty("java.io.tmpdir"));
+ }
+
return targetCt.toClass(classPool.getClassLoader(), null);
}
+
+ private boolean isThrowsSqlException(CtMethod method)
+ {
+ try
+ {
+ for (CtClass clazz : method.getExceptionTypes())
+ {
+ if (clazz.getSimpleName().equals("SQLException"))
+ {
+ return true;
+ }
+ }
+ }
+ catch (NotFoundException e)
+ {
+ // fall thru
+ }
+
+ return false;
+ }
}
diff --git a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java
index 277e70b0..15d05564 100644
--- a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java
+++ b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java
@@ -25,20 +25,39 @@ import java.sql.Statement;
import com.zaxxer.hikari.HikariPool;
/**
- * This class defines the interface for generating proxies, the
- * real (concrete) class is actually generated by Javassist.
+ * Injected proxy factory class.
*
* @author Brett Wooldridge
*/
-public abstract class ProxyFactory
+public final class ProxyFactory
{
- public abstract Connection getProxyConnection(HikariPool pool, Connection connection);
-
- public abstract Statement getProxyStatement(ConnectionProxy connection, Statement statement);
-
- public abstract CallableStatement getProxyCallableStatement(ConnectionProxy connection, CallableStatement statement);
-
- public abstract PreparedStatement getProxyPreparedStatement(ConnectionProxy connection, PreparedStatement statement);
-
- public abstract ResultSet getProxyResultSet(StatementProxy statement, ResultSet resultSet);
+ public static Connection getProxyConnection(HikariPool pool, Connection connection, int defaultIsolationLevel)
+ {
+ // Body is injected by JavassistProxyFactory
+ return null;
+ }
+
+ static Statement getProxyStatement(ConnectionProxy connection, Statement statement)
+ {
+ // Body is injected by JavassistProxyFactory
+ return null;
+ }
+
+ static CallableStatement getProxyCallableStatement(ConnectionProxy connection, CallableStatement statement)
+ {
+ // Body is injected by JavassistProxyFactory
+ return null;
+ }
+
+ static PreparedStatement getProxyPreparedStatement(ConnectionProxy connection, PreparedStatement statement)
+ {
+ // Body is injected by JavassistProxyFactory
+ return null;
+ }
+
+ static ResultSet getProxyResultSet(StatementProxy statement, ResultSet resultSet)
+ {
+ // Body is injected by JavassistProxyFactory
+ return null;
+ }
}
diff --git a/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java b/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java
index f3b771b4..056d3c4a 100644
--- a/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java
+++ b/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java
@@ -28,19 +28,11 @@ import java.sql.Statement;
*/
public abstract class StatementProxy implements Statement
{
- protected static final ProxyFactory PROXY_FACTORY;
-
protected final IHikariConnectionProxy connection;
-
protected final Statement delegate;
private boolean isClosed;
- static
- {
- PROXY_FACTORY = JavassistProxyFactoryFactory.getProxyFactory();
- }
-
protected StatementProxy(IHikariConnectionProxy connection, Statement statement)
{
this.connection = connection;
@@ -56,10 +48,10 @@ public abstract class StatementProxy implements Statement
{
if (resultSet != null)
{
- resultSet = PROXY_FACTORY.getProxyResultSet(this, resultSet);
+ return ProxyFactory.getProxyResultSet(this, resultSet);
}
- return resultSet;
+ return null;
}
// **********************************************************************
diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java
new file mode 100644
index 00000000..ad920daa
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * 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 com.zaxxer.hikari.util;
+
+import java.sql.Statement;
+
+
+/**
+ * Fast list without range checking.
+ *
+ * @author Brett Wooldridge
+ */
+public final class FastStatementList
+{
+ private Statement[] elementData;
+
+ private int size;
+
+ /**
+ * Construct a FastList with a default size of 16.
+ */
+ public FastStatementList()
+ {
+ this.elementData = new Statement[16];
+ }
+
+ /**
+ * Construct a FastList with a specfied size.
+ *
+ * @param size the initial size of the FastList
+ */
+ public FastStatementList(int size)
+ {
+ this.elementData = new Statement[size];
+ }
+
+ /**
+ * Add an element to the tail of the FastList.
+ *
+ * @param element the element to add
+ */
+ public void add(Statement element)
+ {
+ try
+ {
+ elementData[size++] = element;
+ }
+ catch (ArrayIndexOutOfBoundsException oob)
+ {
+ // overflow-conscious code
+ int oldCapacity = elementData.length;
+ int newCapacity = oldCapacity + (oldCapacity >> 1);
+ Statement[] newElementData = new Statement[newCapacity];
+ System.arraycopy(element, 0, newElementData, 0, oldCapacity);
+ newElementData[size++] = element;
+ elementData = newElementData;
+ }
+ }
+
+ /**
+ * Get the element at the specified index.
+ *
+ * @param index the index of the element to get
+ * @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid
+ */
+ public Statement get(int index)
+ {
+ return elementData[index];
+ }
+
+ /**
+ * This remove method is most efficient when the element being removed
+ * is the last element. Equality is identity based, not equals() based.
+ * Only the first matching element is removed.
+ *
+ * @param element the element to remove
+ */
+ public void remove(Object element)
+ {
+ for (int index = size - 1; index >= 0; index--)
+ {
+ if (element == elementData[index])
+ {
+ int numMoved = size - index - 1;
+ if (numMoved > 0)
+ {
+ System.arraycopy(elementData, index + 1, elementData, index, numMoved);
+ }
+ elementData[--size] = null;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Clear the FastList.
+ */
+ public void clear()
+ {
+ for (int i = 0; i < size; i++)
+ {
+ elementData[i] = null;
+ }
+
+ size = 0;
+ }
+
+
+ /**
+ * Get the current number of elements in the FastList.
+ *
+ * @return the number of current elements
+ */
+ public int size()
+ {
+ return size;
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/StatementTest.java b/src/test/java/com/zaxxer/hikari/StatementTest.java
index bf9a22b8..9a458403 100644
--- a/src/test/java/com/zaxxer/hikari/StatementTest.java
+++ b/src/test/java/com/zaxxer/hikari/StatementTest.java
@@ -1,7 +1,6 @@
package com.zaxxer.hikari;
import java.sql.Connection;
-import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@@ -40,7 +39,7 @@ public class StatementTest
}
@Test
- public void testDoubleStatementClose() throws SQLException
+ public void testAutoStatementClose() throws SQLException
{
HikariConfig config = new HikariConfig();
config.setMinimumPoolSize(1);
@@ -63,4 +62,47 @@ public class StatementTest
Assert.assertTrue(statement1.isClosed());
Assert.assertTrue(statement2.isClosed());
}
+
+ @Test
+ public void testDoubleStatementClose() throws SQLException
+ {
+ HikariConfig config = new HikariConfig();
+ config.setMinimumPoolSize(1);
+ config.setMaximumPoolSize(2);
+ config.setAcquireIncrement(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ HikariDataSource ds = new HikariDataSource(config);
+ Connection connection = ds.getConnection();
+
+ Statement statement1 = connection.createStatement();
+
+ statement1.close();
+ statement1.close();
+
+ connection.close();
+ }
+
+ @Test
+ public void testOutOfOrderStatementClose() throws SQLException
+ {
+ HikariConfig config = new HikariConfig();
+ config.setMinimumPoolSize(1);
+ config.setMaximumPoolSize(2);
+ config.setAcquireIncrement(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ HikariDataSource ds = new HikariDataSource(config);
+ Connection connection = ds.getConnection();
+
+ Statement statement1 = connection.createStatement();
+ Statement statement2 = connection.createStatement();
+
+ statement1.close();
+ statement2.close();
+
+ connection.close();
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/UnwrapTest.java b/src/test/java/com/zaxxer/hikari/UnwrapTest.java
new file mode 100644
index 00000000..632c7b2f
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/UnwrapTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * 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 com.zaxxer.hikari;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.zaxxer.hikari.mocks.StubConnection;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class UnwrapTest
+{
+ @Test
+ public void testUnwrapConnection() throws SQLException
+ {
+ HikariConfig config = new HikariConfig();
+ config.setMinimumPoolSize(1);
+ config.setMaximumPoolSize(1);
+ config.setAcquireIncrement(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ HikariDataSource ds = new HikariDataSource(config);
+
+ Assert.assertSame("Idle connections not as expected", 1, ds.pool.getIdleConnections());
+
+ Connection connection = ds.getConnection();
+ Assert.assertNotNull(connection);
+
+ StubConnection unwrapped = connection.unwrap(StubConnection.class);
+ Assert.assertTrue("unwrapped connection is not instance of StubConnection: " + unwrapped, (unwrapped != null && unwrapped instanceof StubConnection));
+ }
+}