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)); + } +}