[#1885] [#2523] Statement.close() may be called upon previously

closed statements

* [#1885] Add test to count opening and closing of Statements and
ResultSets by jOOQ
* [#2523] Statement.close() may be called upon previously closed
statements
This commit is contained in:
Lukas Eder 2013-06-14 18:18:09 +02:00
parent d583f95e04
commit e718d81204
9 changed files with 264 additions and 57 deletions

View File

@ -33,14 +33,12 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
package org.jooq.test._.listeners;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import org.jooq.ExecuteContext;
import org.jooq.impl.DefaultExecuteListener;
import org.junit.Test;
@ -51,38 +49,24 @@ import org.junit.Test;
*
* @author Lukas Eder
*/
public class LifecycleWatcherListener extends DefaultExecuteListener {
public abstract class AbstractLifecycleListener extends DefaultExecuteListener {
/**
* Generated UID
*/
private static final long serialVersionUID = -2283264126211556442L;
private static final long serialVersionUID = -2283264126211556442L;
private static Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
protected static Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getName().compareTo(o2.getName());
}
};
@Override
public int compare(Method o1, Method o2) {
return o1.getName().compareTo(o2.getName());
}
};
public static final Map<Method, Integer> START_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
public static final Map<Method, Integer> END_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
private static final Object INCREMENT_MONITOR = new Object();
private static final Object INCREMENT_MONITOR = new Object();
@Override
public void start(ExecuteContext ctx) {
super.start(ctx);
increment(START_COUNT);
}
@Override
public void end(ExecuteContext ctx) {
super.end(ctx);
increment(END_COUNT);
}
private void increment(Map<Method, Integer> map) {
protected void increment(Map<Method, Integer> map) {
synchronized (INCREMENT_MONITOR) {
Method m = testMethod();

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._.listeners;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.TreeMap;
import org.jooq.ExecuteContext;
import org.jooq.tools.jdbc.DefaultCallableStatement;
import org.jooq.tools.jdbc.DefaultPreparedStatement;
/**
* An <code>ExecuteListener</code> that collects data about the lifecycle of
* JDBC objects in all integration tests.
*
* @author Lukas Eder
*/
public class JDBCLifecycleListener extends AbstractLifecycleListener {
/**
* Generated UID
*/
private static final long serialVersionUID = -2283264126211556442L;
public static final Map<Method, Integer> STMT_START_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
public static final Map<Method, Integer> STMT_CLOSE_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
public static final Map<Method, Integer> RS_START_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
public static final Map<Method, Integer> RS_CLOSE_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
@Override
public void executeStart(ExecuteContext ctx) {
super.executeStart(ctx);
increment(STMT_START_COUNT);
if (ctx.statement() instanceof CallableStatement) {
ctx.statement(new DefaultCallableStatement((CallableStatement) ctx.statement()) {
@Override
public void close() throws SQLException {
increment(STMT_CLOSE_COUNT);
super.close();
}
});
}
else {
ctx.statement(new DefaultPreparedStatement(ctx.statement()) {
@Override
public void close() throws SQLException {
increment(STMT_CLOSE_COUNT);
super.close();
}
});
}
}
}

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._.listeners;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.TreeMap;
import org.jooq.ExecuteContext;
/**
* An <code>ExecuteListener</code> that collects data about the lifecycle of
* execute listeners in all integration tests.
*
* @author Lukas Eder
*/
public class LifecycleWatcherListener extends AbstractLifecycleListener {
/**
* Generated UID
*/
private static final long serialVersionUID = -2283264126211556442L;
public static final Map<Method, Integer> START_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
public static final Map<Method, Integer> END_COUNT = new TreeMap<Method, Integer>(METHOD_COMPARATOR);
@Override
public void start(ExecuteContext ctx) {
super.start(ctx);
increment(START_COUNT);
}
@Override
public void end(ExecuteContext ctx) {
super.end(ctx);
increment(END_COUNT);
}
}

View File

@ -33,7 +33,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
package org.jooq.test._.listeners;
import static org.jooq.conf.ParamType.INLINED;

View File

@ -33,7 +33,7 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
package org.jooq.test._.listeners;
import java.util.HashMap;
import java.util.Map;

View File

@ -462,14 +462,32 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
}
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched) {
assertNotNull(ctx.statement());
checkStatement(ctx, patched, false);
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched, boolean isNull) {
if (isNull) {
assertNull(ctx.statement());
}
else {
assertNotNull(ctx.statement());
}
}
private void checkResultSet(ExecuteContext ctx, boolean patched) {
assertNotNull(ctx.resultSet());
checkResultSet(ctx, patched, false);
}
@SuppressWarnings("unused")
private void checkResultSet(ExecuteContext ctx, boolean patched, boolean isNull) {
if (isNull) {
assertNull(ctx.resultSet());
}
else {
assertNotNull(ctx.resultSet());
}
}
@Override
@ -651,8 +669,8 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
resultEnd = ++callbackCount;
checkBase(ctx);
checkSQL(ctx, true);
checkStatement(ctx, true);
checkResultSet(ctx, true);
checkStatement(ctx, true, true);
checkResultSet(ctx, true, true);
assertNotNull(ctx.record());
assertEquals(2, ctx.record().fieldsRow().size());
@ -665,8 +683,8 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
fetchEnd = ++callbackCount;
checkBase(ctx);
checkSQL(ctx, true);
checkStatement(ctx, true);
checkResultSet(ctx, true);
checkStatement(ctx, true, true);
checkResultSet(ctx, true, true);
assertNotNull(ctx.record());
assertEquals(2, ctx.record().fieldsRow().size());
@ -679,8 +697,8 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
end = ++callbackCount;
checkBase(ctx);
checkSQL(ctx, true);
checkStatement(ctx, true);
checkResultSet(ctx, true);
checkStatement(ctx, true, true);
checkResultSet(ctx, true, true);
assertNotNull(ctx.record());
assertEquals(2, ctx.record().fieldsRow().size());
@ -791,9 +809,18 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
}
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched) {
assertNotNull(ctx.statement());
checkStatement(ctx, patched, false);
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched, boolean isNull) {
if (isNull) {
assertNull(ctx.statement());
}
else {
assertNotNull(ctx.statement());
}
}
@Override
@ -915,7 +942,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
end = ++callbackCount;
checkBase(ctx);
checkSQL(ctx, true);
checkStatement(ctx, true);
checkStatement(ctx, true, true);
}
}
@ -1024,9 +1051,18 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
}
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched) {
assertNotNull(ctx.statement());
checkStatement(ctx, patched, false);
}
@SuppressWarnings("unused")
private void checkStatement(ExecuteContext ctx, boolean patched, boolean isNull) {
if (isNull) {
assertNull(ctx.statement());
}
else {
assertNotNull(ctx.statement());
}
}
@Override
@ -1142,7 +1178,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
end = ++callbackCount;
checkBase(ctx);
checkSQL(ctx, true);
checkStatement(ctx, true);
checkStatement(ctx, true, true);
}
}

View File

@ -93,9 +93,6 @@ import org.jooq.debug.console.Console;
import org.jooq.debug.impl.DebuggerFactory;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.jooq.test._.LifecycleWatcherListener;
import org.jooq.test._.PrettyPrinter;
import org.jooq.test._.TestStatisticsListener;
import org.jooq.test._.converters.Boolean_10;
import org.jooq.test._.converters.Boolean_TF_LC;
import org.jooq.test._.converters.Boolean_TF_UC;
@ -103,6 +100,10 @@ import org.jooq.test._.converters.Boolean_YES_NO_LC;
import org.jooq.test._.converters.Boolean_YES_NO_UC;
import org.jooq.test._.converters.Boolean_YN_LC;
import org.jooq.test._.converters.Boolean_YN_UC;
import org.jooq.test._.listeners.JDBCLifecycleListener;
import org.jooq.test._.listeners.LifecycleWatcherListener;
import org.jooq.test._.listeners.PrettyPrinter;
import org.jooq.test._.listeners.TestStatisticsListener;
import org.jooq.test._.testcases.AggregateWindowFunctionTests;
import org.jooq.test._.testcases.AliasTests;
import org.jooq.test._.testcases.BatchTests;
@ -463,11 +464,10 @@ public abstract class jOOQAbstractTest<
logStat.info("---------------");
logStat.info("Total", total);
logStat.info("");
JooqLogger logLife = JooqLogger.getLogger(LifecycleWatcherListener.class);
logLife.info("EXECUTE LIFECYCLE STATS");
logLife.info("-----------------------");
logStat.info("");
logLife.info("EXECUTE LISTENER LIFECYCLE STATS");
logLife.info("--------------------------------");
int unbalanced = 0;
for (Method m : LifecycleWatcherListener.START_COUNT.keySet()) {
@ -485,6 +485,25 @@ public abstract class jOOQAbstractTest<
}
}
logStat.info("");
logLife.info("JDBC OBJECT LIFECYCLE STATS");
logLife.info("---------------------------");
for (Method m : JDBCLifecycleListener.STMT_START_COUNT.keySet()) {
Integer starts = JDBCLifecycleListener.STMT_START_COUNT.get(m);
Integer ends = JDBCLifecycleListener.STMT_CLOSE_COUNT.get(m);
if (!StringUtils.equals(starts, ends)) {
unbalanced++;
logLife.info(
"Unbalanced", String.format("(open, close): (%1$3s, %2$3s) at %3$s",
starts,
ends == null ? 0 : ends,
m.toString().replace("public void ", "").replaceAll("( throws.*)?", "")));
}
}
logStat.info("");
logLife.info("Unbalanced test: ", unbalanced);
}
@ -884,9 +903,10 @@ public abstract class jOOQAbstractTest<
return DSL.using(create0(settings).configuration().derive(
DefaultExecuteListenerProvider.providers(
new JDBCLifecycleListener(),
new LifecycleWatcherListener(),
new TestStatisticsListener(),
new PrettyPrinter(),
new LifecycleWatcherListener()
new PrettyPrinter()
)
));
}

View File

@ -360,8 +360,6 @@ abstract class AbstractQuery extends AbstractQueryPart implements Query, Attacha
/**
* Default implementation for executable check. Subclasses may override this
* method.
*
* {@inheritDoc}
*/
@Override
public boolean isExecutable() {

View File

@ -1080,11 +1080,15 @@ final class Utils {
* Safely close a statement
*/
static final void safeClose(ExecuteListener listener, ExecuteContext ctx, boolean keepStatement, boolean keepResultSet) {
JDBCUtils.safeClose(ctx.resultSet());
// [#2523] Set JDBC objects to null, to prevent repeated closing
JDBCUtils.safeClose(ctx.resultSet());
ctx.resultSet(null);
// [#385] Close statements only if not requested to keep open
if (!keepStatement)
if (!keepStatement) {
JDBCUtils.safeClose(ctx.statement());
ctx.statement(null);
}
// [#1868] [#2373] Terminate ExecuteListener lifecycle, if needed
if (keepResultSet)