Merge branch 'dev'

* dev: (26 commits)
  Add short-circuit to aliveness check if connection was used within the last second.
  Make slf4j-api scope ‘compile’ and slf4j-simple scope ‘compile’ + optional
  More testy stuff.
  Fixed iteration direction bug, forgot we were iterating backwards.
  Change javassist back from provided to compile so that maven-war-plugin can bundle it by default into the war.
  updated changes log
  Use execute() instead of executeQuery() for initSql.
  Fix isolation level dirty detection logic.
  Correct property name.
  Add the ability for the user to customize Connections before they are added to the pool.
  Implement proper connection unwrapping.
  No need to wrap methods that do not throw SQLException with our own try…catch checkException() logic.
  Remove tools.jar dependency now that instrumentation is gone.  Fix other scopes.
  Added query timeout to test query.
  updated changes log
  Remove unnecessary casts in the generated proxies.
  Replace bound check with try..catch it is faster in the nominal case.
  Track current transaction isolation level so that we can reset it only when necessary (as it often requires a round trip to the server).
  Demote error log to warn.
  Conditionally reset the transaction isolation level based on whether the user has altered it or not.  Turns out it is expensive for some databases.
  ...
pull/30/head
Brett Wooldridge 11 years ago
commit a92e25d436

@ -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

@ -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 \

@ -58,13 +58,12 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
@ -90,17 +89,11 @@
<version>2.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.6.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.snaq</groupId>

@ -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.");

@ -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<IHikariConnectionProxy> 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;
}
}

@ -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 <code>customize()</code> 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;
}

@ -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<String> SQL_ERRORS;
protected final Connection delegate;
private final ArrayList<Statement> 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<Statement>();
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> T unwrap(Class<T> 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));
}
}

@ -36,6 +36,10 @@ public interface IHikariConnectionProxy extends Connection
boolean isBrokenConnection();
boolean isTransactionIsolationDirty();
void resetTransactionIsolationDirty();
long getCreationTime();
long getLastAccess();

@ -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;
}
}

@ -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;
}
}

@ -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;
}
// **********************************************************************

@ -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;
}
}

@ -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();
}
}

@ -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));
}
}
Loading…
Cancel
Save