summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Winkler2008-11-24 10:47:56 (EST)
committerStefan Winkler2008-11-24 10:47:56 (EST)
commita366f575189ee498695c113b0b03cd1a03bfdcbd (patch)
treef2b16bb139709344c7a4fe39ba20168a530e32fd
parent0409e06bec565f7d174f14d5f0f0981d2a816537 (diff)
downloadcdo-a366f575189ee498695c113b0b03cd1a03bfdcbd.zip
cdo-a366f575189ee498695c113b0b03cd1a03bfdcbd.tar.gz
cdo-a366f575189ee498695c113b0b03cd1a03bfdcbd.tar.bz2
[255322] [DB] Possible leakage with prepared statements
https://bugs.eclipse.org/bugs/show_bug.cgi?id=255322
-rw-r--r--features/org.eclipse.emf.cdo.server.product-feature/rootfiles/configuration/cdo-server.xml13
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegate.java16
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegateProvider.java6
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java11
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreFactory.java3
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/AbstractJDBCDelegate.java104
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceReporter.java (renamed from plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceMeasurementWrapper.java)121
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegate.java367
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegateProvider.java24
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegate.java (renamed from plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/NonPreparedStatementJDBCDelegate.java)6
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegateProvider.java9
-rw-r--r--plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/WrappedPreparedStatement.java48
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/CDO AllTests (DB HsqldbPrepStmt).launch20
13 files changed, 618 insertions, 130 deletions
diff --git a/features/org.eclipse.emf.cdo.server.product-feature/rootfiles/configuration/cdo-server.xml b/features/org.eclipse.emf.cdo.server.product-feature/rootfiles/configuration/cdo-server.xml
index 3412499..04bb572 100644
--- a/features/org.eclipse.emf.cdo.server.product-feature/rootfiles/configuration/cdo-server.xml
+++ b/features/org.eclipse.emf.cdo.server.product-feature/rootfiles/configuration/cdo-server.xml
@@ -21,7 +21,18 @@
<property name="toOneReferences" value="LIKE_ATTRIBUTES"/>
</mappingStrategy>
- <jdbcDelegate type="statement" />
+ <!-- Old setting <jdbcDelegate type="statement" />
+ is now replaced by preparedStatement: -->
+ <jdbcDelegate type="preparedStatement" >
+ <!-- to explicitly force prepared statement caching (e.g., if statement pooling is not supported
+ by the JDBC driver, use
+ <property name="cacheStatements" value="enabled" />
+ Else, the implicit default is:
+ <property name="cacheStatements" value="guess" />
+ Which guesses the correct setting based on the JDBC driver's metadata.
+ Also supported is the third setting "disabled".
+ -->
+ </jdbcDelegate>
<dbAdapter name="derby-embedded"/>
<dataSource class="org.apache.derby.jdbc.EmbeddedDataSource"
databaseName="/temp/cdodb1"
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegate.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegate.java
index a34d6b5..46a1d73 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegate.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegate.java
@@ -96,18 +96,14 @@ public interface IJDBCDelegate
public PreparedStatement getPreparedStatement(String sql);
/**
- * Initialize the connection. This must be called once and only once immediately after creating.
- *
- * @param connectionProvider
- * a provider for the DB connection
- * @param readOnly
- * if this is true, all accessors of this instance must not call directly or indirectly any writing
- * operations. Setting readOnly to true leads to the connection's autoCommit property set to true.
+ * Set a connection provider to provide the delegate with the DB connection. This may only be called before
+ * activation.
*/
- public void initConnection(IDBConnectionProvider connectionProvider, boolean readOnly);
+ public void setConnectionProvider(IDBConnectionProvider connectionProvider);
/**
- * Release the DB resources. This has to be called when the delegate is no longer to be used.
+ * Set a flag indicating that this delegate maintains a read-only DB connection. This may only be called before
+ * activation.
*/
- public void release();
+ public void setReadOnly(boolean reader);
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegateProvider.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegateProvider.java
index 4fca9da..4dedb3a 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegateProvider.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/db/IJDBCDelegateProvider.java
@@ -10,6 +10,8 @@
**************************************************************************/
package org.eclipse.emf.cdo.server.db;
+import java.util.Map;
+
/**
* Wraps the creation of JDBCDelegates.
*
@@ -24,4 +26,8 @@ public interface IJDBCDelegateProvider
* This is part of the org.eclipse.emf.cdo.server.db.jdbcDelegateProviders extension point.
*/
public IJDBCDelegate getJDBCDelegate();
+ /**
+ * Set configuration properties
+ */
+ public void setProperties(Map<String, String> properties);
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
index b68e591..b4ff25f 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
@@ -48,6 +48,7 @@ import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBRowHandler;
import org.eclipse.net4j.db.ddl.IDBTable;
import org.eclipse.net4j.util.collection.CloseableIterator;
+import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.trace.ContextTracer;
@@ -76,14 +77,16 @@ public class DBStoreAccessor extends StoreAccessor implements IDBStoreAccessor
{
super(store, session);
jdbcDelegate = store.getJDBCDelegateProvider().getJDBCDelegate();
- jdbcDelegate.initConnection(store.getDBConnectionProvider(), isReader());
+ jdbcDelegate.setConnectionProvider(store.getDBConnectionProvider());
+ jdbcDelegate.setReadOnly(isReader());
}
public DBStoreAccessor(DBStore store, ITransaction transaction) throws DBException
{
super(store, transaction);
jdbcDelegate = store.getJDBCDelegateProvider().getJDBCDelegate();
- jdbcDelegate.initConnection(store.getDBConnectionProvider(), isReader());
+ jdbcDelegate.setConnectionProvider(store.getDBConnectionProvider());
+ jdbcDelegate.setReadOnly(isReader());
}
@Override
@@ -590,13 +593,13 @@ public class DBStoreAccessor extends StoreAccessor implements IDBStoreAccessor
@Override
protected void doActivate() throws Exception
{
- // Do nothing
+ LifecycleUtil.activate(jdbcDelegate);
}
@Override
protected void doDeactivate() throws Exception
{
- jdbcDelegate.release();
+ LifecycleUtil.deactivate(jdbcDelegate);
}
@Override
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreFactory.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreFactory.java
index fd183f6..8f63f5b 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreFactory.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreFactory.java
@@ -71,6 +71,9 @@ public class DBStoreFactory implements IStoreFactory
throw new IllegalArgumentException("Unknown JDBC delegate type: " + delegateProviderType);
}
+ Map<String, String> properties = RepositoryConfigurator.getProperties(delegateProviderConfig, 1);
+ delegateProvider.setProperties(properties);
+
return delegateProvider;
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/AbstractJDBCDelegate.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/AbstractJDBCDelegate.java
index 22bb0a4..d2209dd 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/AbstractJDBCDelegate.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/AbstractJDBCDelegate.java
@@ -26,6 +26,7 @@ import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.IDBConnectionProvider;
import org.eclipse.net4j.util.collection.MoveableList;
+import org.eclipse.net4j.util.lifecycle.Lifecycle;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import java.sql.Connection;
@@ -47,39 +48,45 @@ import java.util.List;
* @author Stefan Winkler
* @since 2.0
*/
-public abstract class AbstractJDBCDelegate implements IJDBCDelegate
+public abstract class AbstractJDBCDelegate extends Lifecycle implements IJDBCDelegate
{
+ private IDBConnectionProvider connectionProvider;
+
+ private boolean readOnly;
+
private Connection connection;
private Statement statement;
- public final Connection getConnection()
+ public AbstractJDBCDelegate()
{
- return connection;
}
- public final void commit(OMMonitor monitor)
+ public IDBConnectionProvider getConnectionProvider()
{
- try
- {
- getConnection().commit();
- }
- catch (SQLException ex)
- {
- throw new DBException(ex);
- }
+ return connectionProvider;
}
- public final void rollback()
+ public void setConnectionProvider(IDBConnectionProvider connectionProvider)
{
- try
- {
- getConnection().rollback();
- }
- catch (SQLException ex)
- {
- throw new DBException(ex);
- }
+ checkInactive();
+ this.connectionProvider = connectionProvider;
+ }
+
+ public boolean isReadOnly()
+ {
+ return readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ checkInactive();
+ this.readOnly = readOnly;
+ }
+
+ public final Connection getConnection()
+ {
+ return connection;
}
public final Statement getStatement()
@@ -111,12 +118,11 @@ public abstract class AbstractJDBCDelegate implements IJDBCDelegate
}
}
- public void initConnection(IDBConnectionProvider connectionProvider, boolean readOnly)
+ public final void commit(OMMonitor monitor)
{
try
{
- connection = connectionProvider.getConnection();
- connection.setAutoCommit(readOnly);
+ getConnection().commit();
}
catch (SQLException ex)
{
@@ -124,10 +130,16 @@ public abstract class AbstractJDBCDelegate implements IJDBCDelegate
}
}
- public void release()
+ public final void rollback()
{
- DBUtil.close(statement);
- DBUtil.close(connection);
+ try
+ {
+ getConnection().rollback();
+ }
+ catch (SQLException ex)
+ {
+ throw new DBException(ex);
+ }
}
public final void insertAttributes(CDORevision revision, IClassMapping classMapping)
@@ -287,6 +299,39 @@ public abstract class AbstractJDBCDelegate implements IJDBCDelegate
}
}
+ @Override
+ protected void doActivate() throws Exception
+ {
+ super.doActivate();
+ connection = connectionProvider.getConnection();
+ connection.setAutoCommit(readOnly);
+ }
+
+ @Override
+ protected void doDeactivate() throws Exception
+ {
+ DBUtil.close(statement);
+ statement = null;
+
+ DBUtil.close(connection);
+ connection = null;
+
+ super.doDeactivate();
+ }
+
+ /**
+ * Release a statement which has been used by the doSelectXxx implementations to create the respective ResultSet. This
+ * must only be called with statements created by subclasses. Subclasses should override to handle special cases like
+ * cached statements which are kept open.
+ *
+ * @param stmt
+ * the statement to close
+ */
+ protected void releaseStatement(Statement stmt)
+ {
+ DBUtil.close(stmt);
+ }
+
/**
* Insert an attribute row.
*/
@@ -335,7 +380,7 @@ public abstract class AbstractJDBCDelegate implements IJDBCDelegate
{
stmt = resultSet.getStatement();
}
- catch (SQLException ex)
+ catch (Exception ex)
{
// Ignore
}
@@ -348,7 +393,8 @@ public abstract class AbstractJDBCDelegate implements IJDBCDelegate
// release it.
if (stmt != statement)
{
- DBUtil.close(stmt);
+ releaseStatement(stmt);
}
}
+
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceMeasurementWrapper.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceReporter.java
index 0402fdd..2fb3f9c 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceMeasurementWrapper.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/JDBCPerformanceReporter.java
@@ -20,6 +20,8 @@ import org.eclipse.emf.cdo.server.db.IReferenceMapping;
import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
import org.eclipse.net4j.db.IDBConnectionProvider;
+import org.eclipse.net4j.util.lifecycle.Lifecycle;
+import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.trace.ContextTracer;
@@ -32,19 +34,31 @@ import java.util.List;
import java.util.Map;
/**
+ * This class is only to be used in tests (there is a static data aggregation member!).
+ *
* @author Stefan Winkler
* @since 2.0
*/
-public class JDBCPerformanceMeasurementWrapper implements IJDBCDelegate
+public class JDBCPerformanceReporter extends Lifecycle implements IJDBCDelegate
{
- private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, JDBCPerformanceMeasurementWrapper.class);
+ private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, JDBCPerformanceReporter.class);
private static Map<String, TimeData> timeData = Collections.synchronizedMap(new HashMap<String, TimeData>());
private IJDBCDelegate delegate;
- public JDBCPerformanceMeasurementWrapper(IJDBCDelegate delegate)
+ public JDBCPerformanceReporter()
+ {
+ }
+
+ public IJDBCDelegate getDelegate()
{
+ return delegate;
+ }
+
+ public void setDelegate(IJDBCDelegate delegate)
+ {
+ checkInactive();
this.delegate = delegate;
}
@@ -68,11 +82,6 @@ public class JDBCPerformanceMeasurementWrapper implements IJDBCDelegate
return delegate.getStatement();
}
- public void initConnection(IDBConnectionProvider connectionProvider, boolean readOnly)
- {
- delegate.initConnection(connectionProvider, readOnly);
- }
-
public void insertAttributes(CDORevision revision, IClassMapping classMapping)
{
long time = System.currentTimeMillis();
@@ -90,11 +99,6 @@ public class JDBCPerformanceMeasurementWrapper implements IJDBCDelegate
registerCall("insertReferenceDbId", time);
}
- public void release()
- {
- delegate.release();
- }
-
public void rollback()
{
delegate.rollback();
@@ -141,6 +145,38 @@ public class JDBCPerformanceMeasurementWrapper implements IJDBCDelegate
registerCall("updateRevisedID", time);
}
+ public void setConnectionProvider(IDBConnectionProvider connectionProvider)
+ {
+ delegate.setConnectionProvider(connectionProvider);
+ }
+
+ public void setReadOnly(boolean reader)
+ {
+ delegate.setReadOnly(reader);
+ }
+
+ @Override
+ protected void doBeforeActivate() throws Exception
+ {
+ super.doBeforeActivate();
+ checkState(delegate, "delegate");
+ }
+
+ @Override
+ protected void doActivate() throws Exception
+ {
+ LifecycleUtil.activate(delegate);
+ super.doActivate();
+ }
+
+ @Override
+ protected void doDeactivate() throws Exception
+ {
+ report();
+ super.doDeactivate();
+ LifecycleUtil.deactivate(delegate);
+ }
+
public static void report()
{
for (TimeData td : timeData.values())
@@ -157,45 +193,50 @@ public class JDBCPerformanceMeasurementWrapper implements IJDBCDelegate
data = new TimeData(method);
timeData.put(method, data);
}
+
data.registerCall(time);
}
-}
-
-class TimeData
-{
- private String method;
- private long numberOfCalls = 0;
+ /**
+ * @author Stefan Winkler
+ * @since 2.0
+ */
+ private static final class TimeData
+ {
+ private String method;
- private long timeMax = Integer.MIN_VALUE;
+ private long numberOfCalls = 0;
- private long timeMin = Integer.MAX_VALUE;
+ private long timeMax = Integer.MIN_VALUE;
- private long timeTotal = 0;
+ private long timeMin = Integer.MAX_VALUE;
- public TimeData(String method)
- {
- this.method = method;
- }
+ private long timeTotal = 0;
- public synchronized void registerCall(long time)
- {
- if (timeMin > time)
+ public TimeData(String method)
{
- timeMin = time;
+ this.method = method;
}
- if (timeMax < time)
+
+ public synchronized void registerCall(long time)
{
- timeMax = time;
+ if (timeMin > time)
+ {
+ timeMin = time;
+ }
+ if (timeMax < time)
+ {
+ timeMax = time;
+ }
+
+ numberOfCalls++;
+ timeTotal += time;
}
- numberOfCalls++;
- timeTotal += time;
- }
-
- public void report(ContextTracer tracer)
- {
- tracer.format("{0}: {1} calls, {2} avg, {3} min, {4} max", method, numberOfCalls, timeTotal / numberOfCalls,
- timeMin, timeMax);
+ public void report(ContextTracer tracer)
+ {
+ tracer.format("{0}: {1} calls, {2} avg, {3} min, {4} max", method, numberOfCalls, timeTotal / numberOfCalls,
+ timeMin, timeMax);
+ }
}
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegate.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegate.java
index aa3f56a..3b4d345 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegate.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegate.java
@@ -21,20 +21,39 @@ import org.eclipse.emf.cdo.spi.common.InternalCDORevision;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBUtil;
+import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.om.trace.ContextTracer;
+import org.eclipse.net4j.util.ref.ReferenceValueMap;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
/**
- * @author Stefan WInkler
+ * @author Stefan Winkler
* @since 2.0
*/
public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
{
+ /**
+ * Value for {@link #cachingFlag}: Guess if caching is needed
+ */
+ public static final String CACHE_STMTS_GUESS = "guess";
+
+ /**
+ * Value for {@link #cachingFlag}: Turn caching on
+ */
+ public static final String CACHE_STMTS_TRUE = "true";
+
+ /**
+ * Value for {@link #cachingFlag}: Turn caching off
+ */
+ public static final String CACHE_STMTS_FALSE = "false";
+
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, PreparedStatementJDBCDelegate.class);
private static final String SQL_UPDATE_REVISE_VERSION = " SET " + CDODBSchema.ATTRIBUTES_REVISED + " = ? WHERE "
@@ -47,47 +66,160 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
private static final String SQL_INSERT_REFERENCE = " VALUES (?, ?, ?, ?)";
+ /**
+ * Cache for preparedStatements used in diverse methods
+ */
+ private Map<CacheKey, WrappedPreparedStatement> statementCache = null;
+
+ /**
+ * This flag determines, if prepared statements should be cached within this delegate. Its value is guessed in
+ * {@link #postInitConnection()} based on the fact if the JDBC driver supports pooled statements. If it does, caching
+ * in the delegate is unnecessary.
+ */
+ private boolean cacheStatements;
+
+ private CachingEnablement cachingEnablement;
+
public PreparedStatementJDBCDelegate()
{
}
+ public CachingEnablement getCachingEnablement()
+ {
+ return cachingEnablement;
+ }
+
+ public void setCachingEnablement(CachingEnablement cachingEnablement)
+ {
+ checkInactive();
+ this.cachingEnablement = cachingEnablement;
+ }
+
@Override
- protected void doInsertAttributes(String tableName, CDORevision rev, List<IAttributeMapping> attributeMappings,
- boolean withFullRevisionInfo)
+ protected void doActivate() throws Exception
{
- PreparedStatement stmt = null;
- StringBuilder sql = new StringBuilder();
- InternalCDORevision revision = (InternalCDORevision)rev;
+ super.doActivate();
- if (attributeMappings == null)
+ switch (cachingEnablement)
{
- attributeMappings = Collections.emptyList();
+ case ENABLED:
+ cacheStatements = true;
+ break;
+
+ case DISABLED:
+ cacheStatements = false;
+ break;
+
+ case GUESS:
+ try
+ {
+ cacheStatements = !getConnection().getMetaData().supportsStatementPooling();
+ }
+ catch (SQLException ex)
+ {
+ OM.LOG.warn("Failed to guess JDBC statement pooling. Activating cache, just to be sure ...", ex);
+ cacheStatements = true;
+ }
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("JDBC PreparedStatement caching is " + (cacheStatements ? "enabled." : "NOT enabled."));
+ }
+
+ if (cacheStatements)
+ {
+ // initialize cache ...
+ statementCache = new ReferenceValueMap.Soft<CacheKey, WrappedPreparedStatement>();
}
+ }
- sql.append("INSERT INTO ");
- sql.append(tableName);
- sql.append(" VALUES (?, ?, ");
- if (withFullRevisionInfo)
+ @Override
+ protected void doBeforeDeactivate() throws Exception
+ {
+ if (cacheStatements)
{
- sql.append("?, ?, ?, ?, ?, ?");
+ for (WrappedPreparedStatement ps : statementCache.values())
+ {
+ DBUtil.close(ps.getWrappedStatement());
+ }
+
+ statementCache.clear();
}
- for (int i = 0; i < attributeMappings.size(); i++)
+ super.doBeforeDeactivate();
+ }
+
+ @Override
+ protected void releaseStatement(Statement stmt)
+ {
+ // leave open cached statements
+ if (!cacheStatements || !(stmt instanceof PreparedStatement))
{
- sql.append(", ?");
+ super.releaseStatement(stmt);
}
- sql.append(")");
- int col = 1;
+ // /* This code would guarantee that releaseStatement is only called
+ // for cached statements. However this looks through the whole hashmap
+ // and is thus too expensive to do in non-debugging mode. */
+ //
+ // else {
+ // if(!selectStatementsCache.containsValue(stmt)) {
+ // super.releaseStatement(stmt);
+ // }
+ // }
+ }
+
+ @Override
+ protected void doInsertAttributes(String tableName, CDORevision rev, List<IAttributeMapping> attributeMappings,
+ boolean withFullRevisionInfo)
+ {
+ InternalCDORevision revision = (InternalCDORevision)rev;
+ if (attributeMappings == null)
+ {
+ attributeMappings = Collections.emptyList();
+ }
+
+ PreparedStatement stmt = null;
+ if (cacheStatements)
+ {
+ stmt = getCachedStatement(StmtType.INSERT_ATTRIBUTES, tableName);
+ }
try
{
+ if (stmt == null)
+ {
+ StringBuilder sql = new StringBuilder();
+
+ sql.append("INSERT INTO ");
+ sql.append(tableName);
+ sql.append(" VALUES (?, ?, ");
+ if (withFullRevisionInfo)
+ {
+ sql.append("?, ?, ?, ?, ?, ?");
+ }
+
+ for (int i = 0; i < attributeMappings.size(); i++)
+ {
+ sql.append(", ?");
+ }
+
+ sql.append(")");
+ stmt = getConnection().prepareStatement(sql.toString());
+
+ if (cacheStatements)
+ {
+ cacheStatement(StmtType.INSERT_ATTRIBUTES, tableName, stmt);
+ }
+ }
+
+ int col = 1;
if (TRACER.isEnabled())
{
- TRACER.format("{0} ({1})", sql.toString(), revision.toString());
+ TRACER.trace(stmt.toString());
}
- stmt = getConnection().prepareStatement(sql.toString());
stmt.setLong(col++, CDOIDUtil.getLong(revision.getID()));
stmt.setInt(col++, revision.getVersion());
if (withFullRevisionInfo)
@@ -114,7 +246,10 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
}
finally
{
- DBUtil.close(stmt);
+ if (!cacheStatements)
+ {
+ DBUtil.close(stmt);
+ }
}
}
@@ -122,15 +257,27 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
protected void doInsertReference(String tableName, int dbID, long source, int version, int index, long target)
{
PreparedStatement stmt = null;
+ if (cacheStatements)
+ {
+ stmt = getCachedStatement(StmtType.INSERT_REFERENCES, tableName);
+ }
try
{
- StringBuilder sql = new StringBuilder("INSERT INTO ");
- sql.append(tableName);
- sql.append(dbID != 0 ? SQL_INSERT_REFERENCE_WITH_DBID : SQL_INSERT_REFERENCE);
+ if (stmt == null)
+ {
+ StringBuilder sql = new StringBuilder("INSERT INTO ");
+ sql.append(tableName);
+ sql.append(dbID != 0 ? SQL_INSERT_REFERENCE_WITH_DBID : SQL_INSERT_REFERENCE);
+ stmt = getConnection().prepareStatement(sql.toString());
+
+ if (cacheStatements)
+ {
+ cacheStatement(StmtType.INSERT_REFERENCES, tableName, stmt);
+ }
+ }
int idx = 1;
- stmt = getConnection().prepareStatement(sql.toString());
if (dbID != 0)
{
stmt.setInt(idx++, dbID);
@@ -142,7 +289,7 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
stmt.setLong(idx++, target);
if (TRACER.isEnabled())
{
- TRACER.format("{0} ({1},{2},{3},{4},{5})", sql.toString(), dbID, source, version, index, target);
+ TRACER.trace(stmt.toString());
}
stmt.execute();
@@ -153,7 +300,10 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
}
finally
{
- DBUtil.close(stmt);
+ if (!cacheStatements)
+ {
+ DBUtil.close(stmt);
+ }
}
}
@@ -161,20 +311,32 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
protected void doUpdateRevised(String tableName, long revisedStamp, long cdoid, int version)
{
PreparedStatement stmt = null;
+ if (cacheStatements)
+ {
+ stmt = getCachedStatement(StmtType.REVISE_VERSION, tableName);
+ }
try
{
- StringBuilder sql = new StringBuilder("UPDATE ");
- sql.append(tableName);
- sql.append(SQL_UPDATE_REVISE_VERSION);
+ if (stmt == null)
+ {
+ StringBuilder sql = new StringBuilder("UPDATE ");
+ sql.append(tableName);
+ sql.append(SQL_UPDATE_REVISE_VERSION);
+ stmt = getConnection().prepareStatement(sql.toString());
+
+ if (cacheStatements)
+ {
+ cacheStatement(StmtType.REVISE_VERSION, tableName, stmt);
+ }
+ }
- stmt = getConnection().prepareStatement(sql.toString());
stmt.setLong(1, revisedStamp);
stmt.setLong(2, cdoid);
stmt.setInt(3, version);
if (TRACER.isEnabled())
{
- TRACER.format("{0} ({1},{2},{3})", sql.toString(), revisedStamp, cdoid, version);
+ TRACER.trace(stmt.toString());
}
stmt.execute();
@@ -185,7 +347,10 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
}
finally
{
- DBUtil.close(stmt);
+ if (!cacheStatements)
+ {
+ DBUtil.close(stmt);
+ }
}
}
@@ -193,19 +358,31 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
protected void doUpdateRevised(String tableName, long revisedStamp, long cdoid)
{
PreparedStatement stmt = null;
+ if (cacheStatements)
+ {
+ stmt = getCachedStatement(StmtType.REVISE_UNREVISED, tableName);
+ }
try
{
- StringBuilder sql = new StringBuilder("UPDATE ");
- sql.append(tableName);
- sql.append(SQL_UPDATE_REVISE_UNREVISED);
+ if (stmt == null)
+ {
+ StringBuilder sql = new StringBuilder("UPDATE ");
+ sql.append(tableName);
+ sql.append(SQL_UPDATE_REVISE_UNREVISED);
+ stmt = getConnection().prepareStatement(sql.toString());
+
+ if (cacheStatements)
+ {
+ cacheStatement(StmtType.REVISE_UNREVISED, tableName, stmt);
+ }
+ }
- stmt = getConnection().prepareStatement(sql.toString());
stmt.setLong(1, revisedStamp);
stmt.setLong(2, cdoid);
if (TRACER.isEnabled())
{
- TRACER.format("{0} ({1},{2})", sql.toString(), revisedStamp, cdoid);
+ TRACER.trace(stmt.toString());
}
stmt.execute();
@@ -216,7 +393,10 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
}
finally
{
- DBUtil.close(stmt);
+ if (!cacheStatements)
+ {
+ DBUtil.close(stmt);
+ }
}
}
@@ -224,6 +404,10 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
protected ResultSet doSelectRevisionAttributes(String tableName, long revisionId,
List<IAttributeMapping> attributeMappings, boolean hasFullRevisionInfo, String where) throws SQLException
{
+ // Because of the variable where clause, statement caching can not be
+ // based on table names. Instead, we build the sql in any case and
+ // use this as key (similar to what JDBC3 does ...).
+
StringBuilder builder = new StringBuilder();
builder.append("SELECT ");
builder.append(CDODBSchema.ATTRIBUTES_VERSION);
@@ -261,7 +445,22 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
TRACER.format("{0} ({1})", sql, revisionId);
}
- PreparedStatement pstmt = getConnection().prepareStatement(sql);
+ PreparedStatement pstmt = null;
+ if (cacheStatements)
+ {
+ pstmt = getCachedStatement(StmtType.GENERAL, sql);
+ if (pstmt == null)
+ {
+ pstmt = getConnection().prepareStatement(sql);
+ cacheStatement(StmtType.GENERAL, sql, pstmt);
+ }
+ }
+ else
+ {
+ /* no caching */
+ pstmt = getConnection().prepareStatement(sql);
+ }
+
pstmt.setLong(1, revisionId);
return pstmt.executeQuery();
}
@@ -300,9 +499,23 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
TRACER.trace(sql);
}
- PreparedStatement pstmt = getConnection().prepareStatement(sql);
- int idx = 1;
+ PreparedStatement pstmt = null;
+ if (cacheStatements)
+ {
+ pstmt = getCachedStatement(StmtType.GENERAL, sql);
+ if (pstmt == null)
+ {
+ pstmt = getConnection().prepareStatement(sql);
+ cacheStatement(StmtType.GENERAL, sql, pstmt);
+ }
+ }
+ else
+ {
+ /* no caching */
+ pstmt = getConnection().prepareStatement(sql);
+ }
+ int idx = 1;
if (dbFeatureID != 0)
{
pstmt.setInt(idx++, dbFeatureID);
@@ -312,4 +525,76 @@ public class PreparedStatementJDBCDelegate extends AbstractJDBCDelegate
pstmt.setInt(idx++, version);
return pstmt.executeQuery();
}
+
+ /**
+ * Cache a prepared statement.
+ */
+ private void cacheStatement(StmtType type, String subKey, PreparedStatement stmt)
+ {
+ statementCache.put(new CacheKey(type, subKey), new WrappedPreparedStatement(stmt));
+ }
+
+ /**
+ * Query the cache of prepared statements.
+ */
+ private PreparedStatement getCachedStatement(StmtType type, String subKey)
+ {
+ WrappedPreparedStatement wrapped = statementCache.get(CacheKey.useOnce(type, subKey));
+ if (wrapped == null)
+ {
+ return null;
+ }
+
+ PreparedStatement stmt = wrapped.getWrappedStatement();
+ if (TRACER.isEnabled())
+ {
+ TRACER.trace("Using cached statement: " + stmt);
+ }
+
+ return stmt;
+ }
+
+ /**
+ * @author Stefan Winkler
+ */
+ public static enum CachingEnablement
+ {
+ ENABLED, DISABLED, GUESS
+ }
+
+ /**
+ * Statement type as first part of the statement cache key.
+ *
+ * @author Stefan Winkler
+ */
+ private static enum StmtType
+ {
+ INSERT_ATTRIBUTES, INSERT_REFERENCES, REVISE_VERSION, REVISE_UNREVISED, GENERAL
+ }
+
+ /**
+ * Convenience definition for Pair<StmtType, String>
+ *
+ * @author Stefan Winkler
+ */
+ private static class CacheKey extends Pair<StmtType, String>
+ {
+ public CacheKey(StmtType type, String subKey)
+ {
+ super(type, subKey);
+ }
+
+ private static CacheKey useOnceKey = new CacheKey(StmtType.GENERAL, "");
+
+ /**
+ * Memory-resource-friendly method which uses a single static field, modifies it and returns it to be used at once,
+ * e.g., to use it in a cache lookup. Do not store a reference to the result!!!
+ */
+ public static CacheKey useOnce(StmtType type, String subKey)
+ {
+ useOnceKey.setElement1(type);
+ useOnceKey.setElement2(subKey);
+ return useOnceKey;
+ }
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegateProvider.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegateProvider.java
index 9b0b235..a166ddf 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegateProvider.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/PreparedStatementJDBCDelegateProvider.java
@@ -12,16 +12,38 @@ package org.eclipse.emf.cdo.server.internal.db.jdbc;
import org.eclipse.emf.cdo.server.db.IJDBCDelegate;
import org.eclipse.emf.cdo.server.db.IJDBCDelegateProvider;
+import org.eclipse.emf.cdo.server.internal.db.jdbc.PreparedStatementJDBCDelegate.CachingEnablement;
+
+import java.util.Map;
/**
* @author Eike Stepper
*/
public class PreparedStatementJDBCDelegateProvider implements IJDBCDelegateProvider
{
+ public static final String CACHE_STATEMENT_PROPERTY_KEY = "cacheStatements";
+
+ private CachingEnablement cachingEnablement = CachingEnablement.GUESS;
+
+ public PreparedStatementJDBCDelegateProvider()
+ {
+ }
public IJDBCDelegate getJDBCDelegate()
{
- return new PreparedStatementJDBCDelegate();
+ PreparedStatementJDBCDelegate delegate = new PreparedStatementJDBCDelegate();
+ delegate.setCachingEnablement(cachingEnablement);
+ return delegate;
}
+ public void setProperties(Map<String, String> properties)
+ {
+ String value = properties.get(CACHE_STATEMENT_PROPERTY_KEY);
+ if (value == null)
+ {
+ value = "GUESS";
+ }
+
+ cachingEnablement = CachingEnablement.valueOf(value.toUpperCase());
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/NonPreparedStatementJDBCDelegate.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegate.java
index a7ba498..ec175f7 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/NonPreparedStatementJDBCDelegate.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegate.java
@@ -31,11 +31,11 @@ import java.util.List;
* @author Stefan Winkler
* @since 2.0
*/
-public class NonPreparedStatementJDBCDelegate extends AbstractJDBCDelegate
+public class StatementJDBCDelegate extends AbstractJDBCDelegate
{
- private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, NonPreparedStatementJDBCDelegate.class);
+ private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, StatementJDBCDelegate.class);
- public NonPreparedStatementJDBCDelegate()
+ public StatementJDBCDelegate()
{
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegateProvider.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegateProvider.java
index fe3c3bc..3e15501 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegateProvider.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/StatementJDBCDelegateProvider.java
@@ -13,6 +13,8 @@ package org.eclipse.emf.cdo.server.internal.db.jdbc;
import org.eclipse.emf.cdo.server.db.IJDBCDelegate;
import org.eclipse.emf.cdo.server.db.IJDBCDelegateProvider;
+import java.util.Map;
+
/**
* @author Eike Stepper
* @since 2.0
@@ -25,6 +27,11 @@ public class StatementJDBCDelegateProvider implements IJDBCDelegateProvider
public IJDBCDelegate getJDBCDelegate()
{
- return new NonPreparedStatementJDBCDelegate();
+ return new StatementJDBCDelegate();
+ }
+
+ public void setProperties(Map<String, String> properties)
+ {
+ // ignore -- no properties for this delegate
}
}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/WrappedPreparedStatement.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/WrappedPreparedStatement.java
new file mode 100644
index 0000000..7efdcb2
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/jdbc/WrappedPreparedStatement.java
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (c) 2004 - 2008 Eike Stepper, Germany.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Eike Stepper - initial API and implementation
+ **************************************************************************/
+package org.eclipse.emf.cdo.server.internal.db.jdbc;
+
+import java.sql.PreparedStatement;
+
+/**
+ * Wrapper for a prepared statement that is cleaned up when it is cached in a WeakReferenceCache and gc'd. Note that
+ * this is just a wrapper with access to its wrapped object. There's no interface delegation, because the interface
+ * delegation would also put the necessity to wrap resultSets and maybe even more, which seems to much overkill for a
+ * simple internal implementation.
+ *
+ * @author Stefan Winkler
+ */
+public class WrappedPreparedStatement
+{
+ private PreparedStatement wrappedStatement = null;
+
+ public WrappedPreparedStatement(PreparedStatement ps)
+ {
+ wrappedStatement = ps;
+ }
+
+ public PreparedStatement getWrappedStatement()
+ {
+ return wrappedStatement;
+ }
+
+ @Override
+ protected void finalize() throws Throwable
+ {
+ wrappedStatement.close();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[Wrapped " + wrappedStatement.toString() + "]";
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo.tests/CDO AllTests (DB HsqldbPrepStmt).launch b/plugins/org.eclipse.emf.cdo.tests/CDO AllTests (DB HsqldbPrepStmt).launch
new file mode 100644
index 0000000..619b47f
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/CDO AllTests (DB HsqldbPrepStmt).launch
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsDBHsqldbPrepStmt.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
+<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
+<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit3"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.emf.cdo.tests.AllTestsDBHsqldbPrepStmt"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.emf.cdo.tests"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms40m&#13;&#10;-Xmx512m"/>
+</launchConfiguration>