From 7f39c2c853614c305d74cdfc3d5850d78180e329 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 11:01:38 +0900 Subject: [PATCH 01/26] Fix grammatical error. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c41175a8..5f758ae7 100644 --- a/CHANGES +++ b/CHANGES @@ -17,7 +17,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 From 355688d50f02d4debb829af3ad7dc43d48ad14ee Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 11:01:55 +0900 Subject: [PATCH 02/26] Fix benchmark script for new version. --- benchmark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 \ From a754c87c8abaf89c4abdfbd328d572351d1ec095 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 11:02:21 +0900 Subject: [PATCH 03/26] Micro optimization, avoids unnecessary assignment. --- src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java b/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java index f3b771b4..183b705c 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java @@ -31,7 +31,6 @@ public abstract class StatementProxy implements Statement protected static final ProxyFactory PROXY_FACTORY; protected final IHikariConnectionProxy connection; - protected final Statement delegate; private boolean isClosed; @@ -56,10 +55,10 @@ public abstract class StatementProxy implements Statement { if (resultSet != null) { - resultSet = PROXY_FACTORY.getProxyResultSet(this, resultSet); + return PROXY_FACTORY.getProxyResultSet(this, resultSet); } - return resultSet; + return null; } // ********************************************************************** From 4a6eb290437db2262f117fbe9fd1a9dfe62eb53c Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 11:03:01 +0900 Subject: [PATCH 04/26] Use our own list implementation which does not do bounds checking, among other optimizations. --- .../zaxxer/hikari/proxy/ConnectionProxy.java | 6 +- .../java/com/zaxxer/hikari/util/FastList.java | 130 ++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/util/FastList.java diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index c4e14a24..bbd98cb4 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.FastList; /** * This is the proxy class for java.sql.Connection. @@ -42,7 +42,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy protected final Connection delegate; - private final ArrayList openStatements; + private final FastList openStatements; private final HikariPool parentPool; private boolean isClosed; @@ -73,7 +73,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy this.delegate = connection; creationTime = lastAccess = System.currentTimeMillis(); - openStatements = new ArrayList(); + openStatements = new FastList(); } public final void unregisterStatement(Object statement) diff --git a/src/main/java/com/zaxxer/hikari/util/FastList.java b/src/main/java/com/zaxxer/hikari/util/FastList.java new file mode 100644 index 00000000..d89f2d4c --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/util/FastList.java @@ -0,0 +1,130 @@ +/* + * 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; + + +/** + * Fast list without range checking. + * + * @author Brett Wooldridge + */ +public class FastList +{ + private transient Object[] elementData; + + private int size; + + /** + * Construct a FastList with a default size of 16. + */ + public FastList() + { + this.elementData = new Object[16]; + } + + /** + * Construct a FastList with a specfied size. + * + * @param size the initial size of the FastList + */ + public FastList(int size) + { + this.elementData = new Object[size]; + } + + /** + * Add an element to the tail of the FastList. + * + * @param element the element to add + */ + public void add(E element) + { + if (size < elementData.length) + { + elementData[size++] = element; + } + else + { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + Object[] newElementData = new Object[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 + */ + @SuppressWarnings("unchecked") + public E get(int index) + { + return (E) elementData[index]; + } + + /** + * This remove method is most efficient when the element being removed + * is the last element. Equality is identity based, not equals() based. + * + * @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; + } +} From 04aa65a6d8df34e80153ff57077c71a98ee8192a Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 13:14:14 +0900 Subject: [PATCH 05/26] =?UTF-8?q?Replace=20ConnectionProxy=E2=80=99s=20Arr?= =?UTF-8?q?ayList=20with=20a=20custom=20class=20(FastStatementL?= =?UTF-8?q?ist),=20replace=20the=20singleton=20generated=20JavassistProxyF?= =?UTF-8?q?actory=20with=20a=20class=20that=20has=20static=20methods=20(so?= =?UTF-8?q?=20we=20get=20invokestatic=20rather=20than=20invokeinterface)?= =?UTF-8?q?=20in=20bytecode.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/zaxxer/hikari/HikariConfig.java | 6 ++ .../java/com/zaxxer/hikari/HikariPool.java | 4 +- .../zaxxer/hikari/proxy/ConnectionProxy.java | 34 +++++------ ...actory.java => JavassistProxyFactory.java} | 61 ++++++++++--------- .../com/zaxxer/hikari/proxy/ProxyFactory.java | 43 +++++++++---- .../zaxxer/hikari/proxy/StatementProxy.java | 9 +-- .../{FastList.java => FastStatementList.java} | 24 ++++---- 7 files changed, 99 insertions(+), 82 deletions(-) rename src/main/java/com/zaxxer/hikari/proxy/{JavassistProxyFactoryFactory.java => JavassistProxyFactory.java} (82%) rename src/main/java/com/zaxxer/hikari/util/{FastList.java => FastStatementList.java} (85%) diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index 97310a36..d8557b03 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 @@ -64,6 +65,11 @@ public final class HikariConfig implements HikariConfigMBean private Properties dataSourceProperties; private DataSource dataSource; + static + { + JavassistProxyFactory.initialize(); + } + /** * Default constructor */ diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 966da0be..e4b9aef7 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; /** @@ -325,7 +325,7 @@ public final class HikariPool implements HikariPoolMBean try { Connection connection = dataSource.getConnection(); - IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) JavassistProxyFactoryFactory.getProxyFactory().getProxyConnection(this, connection); + IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) ProxyFactory.getProxyConnection(this, connection); if (transactionIsolation < 0) { diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index bbd98cb4..e8885c80 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -27,7 +27,7 @@ import java.util.Timer; import java.util.TimerTask; import com.zaxxer.hikari.HikariPool; -import com.zaxxer.hikari.util.FastList; +import com.zaxxer.hikari.util.FastStatementList; /** * This is the proxy class for java.sql.Connection. @@ -36,13 +36,11 @@ import com.zaxxer.hikari.util.FastList; */ public abstract class ConnectionProxy implements IHikariConnectionProxy { - private static final ProxyFactory PROXY_FACTORY; - private static final Set SQL_ERRORS; protected final Connection delegate; - private final FastList openStatements; + private final FastStatementList openStatements; private final HikariPool parentPool; private boolean isClosed; @@ -63,8 +61,6 @@ 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) @@ -73,7 +69,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy this.delegate = connection; creationTime = lastAccess = System.currentTimeMillis(); - openStatements = new FastList(); + openStatements = new FastStatementList(); } public final void unregisterStatement(Object statement) @@ -409,61 +405,61 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy 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/JavassistProxyFactoryFactory.java b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java similarity index 82% rename from src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactoryFactory.java rename to src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java index ac6ae306..ef406ce8 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactoryFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java @@ -31,16 +31,16 @@ import javassist.CtNewMethod; import javassist.LoaderClassPath; import javassist.Modifier; +import org.slf4j.LoggerFactory; + 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 +48,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 +63,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"); @@ -89,51 +93,43 @@ public final class JavassistProxyFactoryFactory } } - public static ProxyFactory getProxyFactory() - { - return proxyFactory; - } - - private ProxyFactory generateProxyFactory() throws Exception + private void modifyProxyFactory() 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); } /** @@ -198,6 +194,11 @@ public final class JavassistProxyFactoryFactory } } + if (LoggerFactory.getLogger(getClass()).isDebugEnabled()) + { + targetCt.debugWriteFile(System.getProperty("java.io.tmpdir")); + } + return targetCt.toClass(classPool.getClassLoader(), null); } } diff --git a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java index 277e70b0..ba20ab20 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) + { + // 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 183b705c..056d3c4a 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/StatementProxy.java @@ -28,18 +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; @@ -55,7 +48,7 @@ public abstract class StatementProxy implements Statement { if (resultSet != null) { - return PROXY_FACTORY.getProxyResultSet(this, resultSet); + return ProxyFactory.getProxyResultSet(this, resultSet); } return null; diff --git a/src/main/java/com/zaxxer/hikari/util/FastList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java similarity index 85% rename from src/main/java/com/zaxxer/hikari/util/FastList.java rename to src/main/java/com/zaxxer/hikari/util/FastStatementList.java index d89f2d4c..f919fb6f 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -16,24 +16,26 @@ package com.zaxxer.hikari.util; +import java.sql.Statement; + /** * Fast list without range checking. * * @author Brett Wooldridge */ -public class FastList +public final class FastStatementList { - private transient Object[] elementData; + private Statement[] elementData; private int size; /** * Construct a FastList with a default size of 16. */ - public FastList() + public FastStatementList() { - this.elementData = new Object[16]; + this.elementData = new Statement[16]; } /** @@ -41,9 +43,9 @@ public class FastList * * @param size the initial size of the FastList */ - public FastList(int size) + public FastStatementList(int size) { - this.elementData = new Object[size]; + this.elementData = new Statement[size]; } /** @@ -51,7 +53,7 @@ public class FastList * * @param element the element to add */ - public void add(E element) + public void add(Statement element) { if (size < elementData.length) { @@ -62,7 +64,7 @@ public class FastList // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); - Object[] newElementData = new Object[newCapacity]; + Statement[] newElementData = new Statement[newCapacity]; System.arraycopy(element, 0, newElementData, 0, oldCapacity); newElementData[size++] = element; elementData = newElementData; @@ -75,15 +77,15 @@ public class FastList * @param index the index of the element to get * @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid */ - @SuppressWarnings("unchecked") - public E get(int index) + public Statement get(int index) { - return (E) elementData[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 */ From c6b8d488a4ab5e84f1594068e87bb653441e705a Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 15:38:22 +0900 Subject: [PATCH 06/26] Conditionally reset the transaction isolation level based on whether the user has altered it or not. Turns out it is expensive for some databases. --- .../java/com/zaxxer/hikari/HikariPool.java | 9 +++++-- .../zaxxer/hikari/proxy/ConnectionProxy.java | 27 +++++++++++++++++++ .../hikari/proxy/IHikariConnectionProxy.java | 4 +++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index e4b9aef7..e26f97e5 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -388,7 +388,7 @@ 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) { @@ -398,7 +398,10 @@ public final class HikariPool implements HikariPoolMBean try { connection.setAutoCommit(isAutoCommit); - connection.setTransactionIsolation(transactionIsolation); + if (connection.isTransactionIsolationDirty()) + { + connection.setTransactionIsolation(transactionIsolation); + } try { @@ -418,6 +421,8 @@ public final class HikariPool implements HikariPoolMBean { connection.commit(); } + + connection.resetTransactionIsolationDirty(); } return true; diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index e8885c80..a6e7b7e0 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -45,6 +45,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private boolean isClosed; private boolean forceClose; + private boolean isTransactionIsolationDirty; private final long creationTime; private volatile long lastAccess; @@ -113,6 +114,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; @@ -399,6 +410,22 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy } } + /** {@inheritDoc} */ + public void setTransactionIsolation(int level) throws SQLException + { + checkClosed(); + try + { + delegate.setTransactionIsolation(level); + isTransactionIsolationDirty = true; + } + catch (SQLException e) + { + checkException(e); + throw e; + } + } + // ********************************************************************** // Private Methods // ********************************************************************** 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(); From 395035b5f533fa24da17de4fd5d40787a7e9652f Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 15 Jan 2014 16:23:50 +0900 Subject: [PATCH 07/26] Conditionally reset the transaction isolation level based on whether the user has altered it or not. Turns out it is expensive for some databases. --- src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index a6e7b7e0..c4fe4a14 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -71,6 +71,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy creationTime = lastAccess = System.currentTimeMillis(); openStatements = new FastStatementList(); + isTransactionIsolationDirty = true; } public final void unregisterStatement(Object statement) From 430dd730e7343aa6d31d60a73d6d421955676805 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 00:13:07 +0900 Subject: [PATCH 08/26] Demote error log to warn. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index e26f97e5..f5d662a6 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -429,7 +429,7 @@ public final class HikariPool implements HikariPoolMBean } 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; } } From 8b440dd79c4630dd84fb3aaac7e339f256a6d0c0 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 09:43:09 +0900 Subject: [PATCH 09/26] Track current transaction isolation level so that we can reset it only when necessary (as it often requires a round trip to the server). --- src/main/java/com/zaxxer/hikari/HikariPool.java | 13 ++++++------- .../com/zaxxer/hikari/proxy/ConnectionProxy.java | 8 +++++--- .../java/com/zaxxer/hikari/proxy/ProxyFactory.java | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index f5d662a6..a867625b 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -325,20 +325,19 @@ public final class HikariPool implements HikariPoolMBean try { Connection connection = dataSource.getConnection(); - IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) ProxyFactory.getProxyConnection(this, connection); - + + connection.setAutoCommit(isAutoCommit); if (transactionIsolation < 0) { transactionIsolation = connection.getTransactionIsolation(); } - - boolean alive = isConnectionAlive(proxyConnection, configuration.getConnectionTimeout()); - if (!alive) + else { - // This will be caught below... - throw new RuntimeException("Connection not alive, retry."); + connection.setTransactionIsolation(transactionIsolation); } + IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) ProxyFactory.getProxyConnection(this, connection, transactionIsolation); + String initSql = configuration.getConnectionInitSql(); if (initSql != null && initSql.length() > 0) { diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index c4fe4a14..13d1b510 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -46,6 +46,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private boolean isClosed; private boolean forceClose; private boolean isTransactionIsolationDirty; + private int currentIsolationLevel; private final long creationTime; private volatile long lastAccess; @@ -64,14 +65,14 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy SQL_ERRORS.add("01002"); // SQL92 disconnect error } - protected ConnectionProxy(HikariPool pool, Connection connection) + protected ConnectionProxy(HikariPool pool, Connection connection, int defaultIsolationLevel) { this.parentPool = pool; this.delegate = connection; + this.currentIsolationLevel = defaultIsolationLevel; creationTime = lastAccess = System.currentTimeMillis(); openStatements = new FastStatementList(); - isTransactionIsolationDirty = true; } public final void unregisterStatement(Object statement) @@ -418,7 +419,8 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy try { delegate.setTransactionIsolation(level); - isTransactionIsolationDirty = true; + isTransactionIsolationDirty |= (currentIsolationLevel == level); + currentIsolationLevel = level; } catch (SQLException e) { diff --git a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java index ba20ab20..15d05564 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java @@ -31,7 +31,7 @@ import com.zaxxer.hikari.HikariPool; */ public final class ProxyFactory { - public static Connection getProxyConnection(HikariPool pool, Connection connection) + public static Connection getProxyConnection(HikariPool pool, Connection connection, int defaultIsolationLevel) { // Body is injected by JavassistProxyFactory return null; From 352a269ad8d99c4c2942dfcca5ad24486154ba78 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 11:31:45 +0900 Subject: [PATCH 10/26] Replace bound check with try..catch it is faster in the nominal case. --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index f919fb6f..89ed851e 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -55,11 +55,11 @@ public final class FastStatementList */ public void add(Statement element) { - if (size < elementData.length) + try { elementData[size++] = element; } - else + catch (ArrayIndexOutOfBoundsException oob) { // overflow-conscious code int oldCapacity = elementData.length; From 128b169f99d4b4a43b828e4e836b37941f5bc260 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 11:32:14 +0900 Subject: [PATCH 11/26] Remove unnecessary casts in the generated proxies. --- .../zaxxer/hikari/proxy/JavassistProxyFactory.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java index ef406ce8..0d811408 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java @@ -77,15 +77,18 @@ public final class JavassistProxyFactory 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) { From 073630973ad63f8eef9fb530c534458f5b57ab57 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 11:39:46 +0900 Subject: [PATCH 12/26] updated changes log --- CHANGES | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index 5f758ae7..190e1e4d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,22 @@ 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. + Changes between 1.2.5 and 1.2.6 *) Fixed regression that caused IndexOutOfBounds when multiple unclosed From b388227dc44a976e08908de7f242e44a5089200b Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 17:53:09 +0900 Subject: [PATCH 13/26] Added query timeout to test query. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index a867625b..d4d44be8 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -411,6 +411,7 @@ public final class HikariPool implements HikariPoolMBean try (Statement statement = connection.createStatement()) { + statement.setQueryTimeout((int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs)); statement.executeQuery(configuration.getConnectionTestQuery()); } } From b5b098042ad51ddad28949c4cf7c61c2eb91e202 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 16 Jan 2014 18:57:50 +0900 Subject: [PATCH 14/26] Remove tools.jar dependency now that instrumentation is gone. Fix other scopes. --- pom.xml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 582f3502..1611121f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ org.slf4j slf4j-api 1.7.5 - compile + provided org.slf4j @@ -90,17 +90,11 @@ 2.3.0 test - - com.sun - tools - 1.6.0 - system - ${java.home}/../lib/tools.jar - org.javassist javassist 3.18.1-GA + provided net.snaq From 56e97a8e3c5a31313943514d70ad65ef51a513b8 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 09:58:39 +0900 Subject: [PATCH 15/26] =?UTF-8?q?No=20need=20to=20wrap=20methods=20that=20?= =?UTF-8?q?do=20not=20throw=20SQLException=20with=20our=20own=20try?= =?UTF-8?q?=E2=80=A6catch=20checkException()=20logic.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hikari/proxy/JavassistProxyFactory.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java index 0d811408..5598a551 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java @@ -30,6 +30,7 @@ import javassist.CtMethod; import javassist.CtNewMethod; import javassist.LoaderClassPath; import javassist.Modifier; +import javassist.NotFoundException; import org.slf4j.LoggerFactory; @@ -186,7 +187,16 @@ public final class JavassistProxyFactory 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", ""); @@ -204,4 +214,24 @@ public final class JavassistProxyFactory 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; + } } From a752582368db6b9cc1ca839927896aaf80d570e9 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 09:58:58 +0900 Subject: [PATCH 16/26] Implement proper connection unwrapping. --- .../zaxxer/hikari/proxy/ConnectionProxy.java | 19 +++++++ .../java/com/zaxxer/hikari/UnwrapTest.java | 53 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/test/java/com/zaxxer/hikari/UnwrapTest.java diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 13d1b510..e6361e26 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -429,6 +429,25 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy } } + /** {@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 // ********************************************************************** 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)); + } +} From 94aa31510f3dee21150f76275cc25844fc8b3ca4 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 10:35:35 +0900 Subject: [PATCH 17/26] Add the ability for the user to customize Connections before they are added to the pool. --- .../java/com/zaxxer/hikari/HikariConfig.java | 34 ++++++++++++--- .../java/com/zaxxer/hikari/HikariPool.java | 35 ++++++++++++---- .../zaxxer/hikari/IConnectionCustomizer.java | 41 +++++++++++++++++++ 3 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/IConnectionCustomizer.java diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index d8557b03..0600fa38 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -50,20 +50,21 @@ 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 connectionCustomizationClass; + 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 { @@ -178,6 +179,16 @@ public final class HikariConfig implements HikariConfigMBean this.acquireRetryDelay = acquireRetryDelayMs; } + public String getConnectionCustomizationClass() + { + return connectionCustomizationClass; + } + + public void setConnectionCustomizationClass(String connectionCustomizationClass) + { + this.connectionCustomizationClass = connectionCustomizationClass; + } + public String getConnectionTestQuery() { return connectionTestQuery; @@ -407,6 +418,19 @@ public final class HikariConfig implements HikariConfigMBean acquireRetryDelay = ACQUIRE_RETRY_DELAY; } + if (connectionCustomizationClass != null) + { + try + { + getClass().getClassLoader().loadClass(connectionCustomizationClass); + } + catch (ClassNotFoundException e) + { + logger.warn("connectionCustomizationClass specified class '" + connectionCustomizationClass + "' could not be loaded", e); + connectionCustomizationClass = 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 d4d44be8..3beb7dd0 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -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.getConnectionCustomizationClass() != null) + { + try + { + Class clazz = this.getClass().getClassLoader().loadClass(configuration.getConnectionCustomizationClass()); + 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); @@ -326,14 +343,14 @@ public final class HikariPool implements HikariPoolMBean { Connection connection = dataSource.getConnection(); - connection.setAutoCommit(isAutoCommit); if (transactionIsolation < 0) { transactionIsolation = connection.getTransactionIsolation(); } - else + + if (connectionCustomizer != null) { - connection.setTransactionIsolation(transactionIsolation); + connectionCustomizer.customize(connection); } IHikariConnectionProxy proxyConnection = (IHikariConnectionProxy) ProxyFactory.getProxyConnection(this, connection, transactionIsolation); 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; +} From eda62f74426e899d3c4fd8b600cb74d96bfef082 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 10:53:46 +0900 Subject: [PATCH 18/26] Correct property name. --- .../java/com/zaxxer/hikari/HikariConfig.java | 18 +++++++++--------- .../java/com/zaxxer/hikari/HikariPool.java | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index 0600fa38..f4e546c2 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -56,7 +56,7 @@ public final class HikariConfig implements HikariConfigMBean // Properties NOT changeable at runtime // private int transactionIsolation; - private String connectionCustomizationClass; + private String connectionCustomizerClassName; private String connectionInitSql; private String connectionTestQuery; private String dataSourceClassName; @@ -179,14 +179,14 @@ public final class HikariConfig implements HikariConfigMBean this.acquireRetryDelay = acquireRetryDelayMs; } - public String getConnectionCustomizationClass() + public String getConnectionCustomizerClassName() { - return connectionCustomizationClass; + return connectionCustomizerClassName; } - public void setConnectionCustomizationClass(String connectionCustomizationClass) + public void setConnectionCustomizerClassName(String connectionCustomizerClassName) { - this.connectionCustomizationClass = connectionCustomizationClass; + this.connectionCustomizerClassName = connectionCustomizerClassName; } public String getConnectionTestQuery() @@ -418,16 +418,16 @@ public final class HikariConfig implements HikariConfigMBean acquireRetryDelay = ACQUIRE_RETRY_DELAY; } - if (connectionCustomizationClass != null) + if (connectionCustomizerClassName != null) { try { - getClass().getClassLoader().loadClass(connectionCustomizationClass); + getClass().getClassLoader().loadClass(connectionCustomizerClassName); } catch (ClassNotFoundException e) { - logger.warn("connectionCustomizationClass specified class '" + connectionCustomizationClass + "' could not be loaded", e); - connectionCustomizationClass = null; + logger.warn("connectionCustomizationClass specified class '" + connectionCustomizerClassName + "' could not be loaded", e); + connectionCustomizerClassName = null; } } diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 3beb7dd0..97ef07f5 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -101,11 +101,11 @@ public final class HikariPool implements HikariPoolMBean this.dataSource = configuration.getDataSource(); } - if (configuration.getConnectionCustomizationClass() != null) + if (configuration.getConnectionCustomizerClassName() != null) { try { - Class clazz = this.getClass().getClassLoader().loadClass(configuration.getConnectionCustomizationClass()); + Class clazz = this.getClass().getClassLoader().loadClass(configuration.getConnectionCustomizerClassName()); this.connectionCustomizer = (IConnectionCustomizer) clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) From 9021e7178d3c9322f04ae8b7c14dd61e61c14ccc Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 11:12:53 +0900 Subject: [PATCH 19/26] Fix isolation level dirty detection logic. --- src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index e6361e26..391db997 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -42,11 +42,11 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private final FastStatementList openStatements; private final HikariPool parentPool; + private final int defaultIsolationLevel; private boolean isClosed; private boolean forceClose; private boolean isTransactionIsolationDirty; - private int currentIsolationLevel; private final long creationTime; private volatile long lastAccess; @@ -69,7 +69,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy { this.parentPool = pool; this.delegate = connection; - this.currentIsolationLevel = defaultIsolationLevel; + this.defaultIsolationLevel = defaultIsolationLevel; creationTime = lastAccess = System.currentTimeMillis(); openStatements = new FastStatementList(); @@ -419,8 +419,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy try { delegate.setTransactionIsolation(level); - isTransactionIsolationDirty |= (currentIsolationLevel == level); - currentIsolationLevel = level; + isTransactionIsolationDirty = (level != defaultIsolationLevel); } catch (SQLException e) { From 069713c94fc2104330ba20f733daa233a1899440 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 15:00:30 +0900 Subject: [PATCH 20/26] Use execute() instead of executeQuery() for initSql. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 97ef07f5..92d2b79b 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -361,7 +361,7 @@ public final class HikariPool implements HikariPoolMBean connection.setAutoCommit(true); try (Statement statement = connection.createStatement()) { - statement.executeQuery(initSql); + statement.execute(initSql); } } From f1b0f5fe24cb6320af819be3532e500a9c4770f2 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 15:00:43 +0900 Subject: [PATCH 21/26] updated changes log --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 190e1e4d..d8cc6725 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,10 @@ Changes between 1.2.6 and 1.2.7 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 From d10147363a2cf206ec5ab75caf1b6d53e9cbe321 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 15:01:33 +0900 Subject: [PATCH 22/26] Change javassist back from provided to compile so that maven-war-plugin can bundle it by default into the war. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1611121f..ff542d72 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ org.javassist javassist 3.18.1-GA - provided + compile net.snaq From d4178863659c7f911f0833c8c126e97e7bbcf702 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 16:12:00 +0900 Subject: [PATCH 23/26] Fixed iteration direction bug, forgot we were iterating backwards. --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index 89ed851e..ad920daa 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -91,7 +91,7 @@ public final class FastStatementList */ public void remove(Object element) { - for (int index = size - 1; index >= 0; index++) + for (int index = size - 1; index >= 0; index--) { if (element == elementData[index]) { From d4caabaa13f79765057a0a06465213ccc124f30c Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 16:12:19 +0900 Subject: [PATCH 24/26] More testy stuff. --- .../java/com/zaxxer/hikari/StatementTest.java | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) 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(); + } } From c2b3fe4ff7d49f9aaa2f812baf6b42f24c84773e Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Fri, 17 Jan 2014 16:13:52 +0900 Subject: [PATCH 25/26] =?UTF-8?q?Make=20slf4j-api=20scope=20=E2=80=98compi?= =?UTF-8?q?le=E2=80=99=20and=20slf4j-simple=20scope=20=E2=80=98compile?= =?UTF-8?q?=E2=80=99=20+=20optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ff542d72..1528efc3 100644 --- a/pom.xml +++ b/pom.xml @@ -58,13 +58,12 @@ org.slf4j slf4j-api 1.7.5 - provided org.slf4j slf4j-simple 1.7.5 - test + true org.mockito From 95d3fbdf0bd3f38db710bbee746727942a7f7b43 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Sat, 18 Jan 2014 23:30:38 +0900 Subject: [PATCH 26/26] Add short-circuit to aliveness check if connection was used within the last second. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 92d2b79b..20d24f4e 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -406,11 +406,6 @@ public final class HikariPool implements HikariPoolMBean */ private boolean isConnectionAlive(final IHikariConnectionProxy connection, long timeoutMs) { - if (timeoutMs < 1000) - { - timeoutMs = 1000; - } - try { connection.setAutoCommit(isAutoCommit); @@ -419,8 +414,19 @@ public final class HikariPool implements HikariPoolMBean 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));