You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2014/02/23 19:59:38 UTC

git commit: METAMODEL-38: Added pooling of Connection+PreparedStatement in JdbcCompiledQuery

Repository: incubator-metamodel
Updated Branches:
  refs/heads/master e84630b1d -> 63659d330


METAMODEL-38: Added pooling of Connection+PreparedStatement in
JdbcCompiledQuery

Project: http://git-wip-us.apache.org/repos/asf/incubator-metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-metamodel/commit/63659d33
Tree: http://git-wip-us.apache.org/repos/asf/incubator-metamodel/tree/63659d33
Diff: http://git-wip-us.apache.org/repos/asf/incubator-metamodel/diff/63659d33

Branch: refs/heads/master
Commit: 63659d33088e97df0cfbb035ccbc45aeae69dc54
Parents: e84630b
Author: Kasper Sørensen <i....@gmail.com>
Authored: Sun Feb 23 19:59:41 2014 +0100
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Sun Feb 23 19:59:41 2014 +0100

----------------------------------------------------------------------
 jdbc/pom.xml                                    |   6 +
 .../metamodel/jdbc/JdbcCompiledQuery.java       | 218 ++++++++++---------
 .../jdbc/JdbcCompiledQueryLeaseFactory.java     |  19 +-
 .../apache/metamodel/jdbc/JdbcDataContext.java  |   4 +
 .../metamodel/jdbc/JdbcDataContextTest.java     |  97 ++++++++-
 5 files changed, 233 insertions(+), 111 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/63659d33/jdbc/pom.xml
----------------------------------------------------------------------
diff --git a/jdbc/pom.xml b/jdbc/pom.xml
index 348092f..870968f 100644
--- a/jdbc/pom.xml
+++ b/jdbc/pom.xml
@@ -54,6 +54,12 @@ under the License.
 			<artifactId>easymock</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>commons-dbcp</groupId>
+			<artifactId>commons-dbcp</artifactId>
+			<version>1.4</version>
+			<scope>test</scope>
+		</dependency>
 
 		<!-- Various freely available database drivers -->
 		<dependency>

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/63659d33/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQuery.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQuery.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQuery.java
index c69f5df..a78ec1e 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQuery.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQuery.java
@@ -18,12 +18,12 @@
  */
 package org.apache.metamodel.jdbc;
 
-import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.commons.pool.impl.GenericObjectPool.Config;
 import org.apache.metamodel.MetaModelException;
 import org.apache.metamodel.query.CompiledQuery;
 import org.apache.metamodel.query.DefaultCompiledQuery;
@@ -38,103 +38,119 @@ import org.slf4j.LoggerFactory;
  */
 final class JdbcCompiledQuery extends DefaultCompiledQuery implements CompiledQuery {
 
-	private static final Logger logger = LoggerFactory.getLogger(JdbcCompiledQuery.class);
-
-	private final JdbcDataContext _dataContext;
-	private final Connection _connection;
-	private final String _sql;
-	private final Query _query;
-	private final GenericObjectPool<JdbcCompiledQueryLease> _pool;
-	private boolean _closed;
-
-	public JdbcCompiledQuery(JdbcDataContext dc, Query query) {
-		super(query);
-		_dataContext = dc;
-		_connection = dc.getConnection();
-		_query = query;
-		_sql = dc.getQueryRewriter().rewriteQuery(query);
-		_pool = new GenericObjectPool<JdbcCompiledQueryLease>(new JdbcCompiledQueryLeaseFactory(dc, _connection, _sql));
-		_closed = false;
-
-		logger.debug("Created compiled JDBC query: {}", _sql);
-	}
-
-	public JdbcCompiledQueryLease borrowLease() {
-		if (logger.isDebugEnabled()) {
-			logger.debug("Borrowing lease. Leases (before): Active={}, Idle={}", getActiveLeases(), getIdleLeases());
-		}
-		try {
-			return _pool.borrowObject();
-		} catch (Exception e) {
-			throw handleError(e, "borrow lease");
-		}
-	}
-
-	public void returnLease(JdbcCompiledQueryLease lease) {
-		if (logger.isDebugEnabled()) {
-			logger.debug("Returning lease. Leases (before): Active={}, Idle={}", getActiveLeases(), getIdleLeases());
-		}
-		try {
-			_pool.returnObject(lease);
-		} catch (Exception e) {
-			throw handleError(e, "return lease");
-		}
-	}
-
-	private RuntimeException handleError(Exception e, String message) {
-		if (logger.isWarnEnabled()) {
-			logger.warn("Unexpected error occurred in compiled JDBC query: " + message, e);
-		}
-		
-		if (e instanceof SQLException) {
-			return JdbcUtils.wrapException((SQLException) e, message);
-		} else if (e instanceof RuntimeException) {
-			return (RuntimeException) e;
-		} else {
-			return new MetaModelException(message, e);
-		}
-	}
-
-	protected int getActiveLeases() {
-		return _pool.getNumActive();
-	}
-
-	protected int getIdleLeases() {
-		return _pool.getNumIdle();
-	}
-
-	protected Query getQuery() {
-		return _query;
-	}
-
-	@Override
-	public String toSql() {
-		return _sql;
-	}
-
-	@Override
-	public void close() {
-		logger.debug("Closing compiled JDBC query: {}", _sql);
-		try {
-			_pool.close();
-		} catch (Exception e) {
-			throw handleError(e, "close pool of leases");
-		} finally {
-			_dataContext.close(_connection, null, null);
-			_closed = true;
-		}
-	}
-
-	protected List<SelectItem> getSelectItems() {
-		return _query.getSelectClause().getItems();
-	}
-
-	@Override
-	protected void finalize() throws Throwable {
-		super.finalize();
-		if (!_closed) {
-			logger.warn("finalize() invoked, but DataSet is not closed. Invoking close() on {}", this);
-			close();
-		}
-	}
-}
+    private static final Logger logger = LoggerFactory.getLogger(JdbcCompiledQuery.class);
+
+    private final String _sql;
+    private final Query _query;
+    private final GenericObjectPool<JdbcCompiledQueryLease> _pool;
+    private boolean _closed;
+
+    public JdbcCompiledQuery(JdbcDataContext dc, Query query) {
+        super(query);
+        _query = query;
+        _sql = dc.getQueryRewriter().rewriteQuery(query);
+
+        final Config config = new Config();
+        config.maxActive = getSystemPropertyValue(JdbcDataContext.SYSTEM_PROPERTY_COMPILED_QUERY_POOL_MAX_SIZE, -1);
+        config.minEvictableIdleTimeMillis = getSystemPropertyValue(
+                JdbcDataContext.SYSTEM_PROPERTY_COMPILED_QUERY_POOL_MIN_EVICTABLE_IDLE_TIME_MILLIS, 500);
+        config.timeBetweenEvictionRunsMillis = getSystemPropertyValue(
+                JdbcDataContext.SYSTEM_PROPERTY_COMPILED_QUERY_POOL_TIME_BETWEEN_EVICTION_RUNS_MILLIS, 1000);
+
+        _pool = new GenericObjectPool<JdbcCompiledQueryLease>(new JdbcCompiledQueryLeaseFactory(dc, _sql), config);
+        _closed = false;
+
+        logger.debug("Created compiled JDBC query: {}", _sql);
+    }
+
+    private int getSystemPropertyValue(String property, int defaultValue) {
+        String str = System.getProperty(property);
+        if (str == null) {
+            return defaultValue;
+        }
+        try {
+            return Integer.parseInt(str);
+        } catch (NumberFormatException e) {
+            logger.debug("Failed to parse system property '{}': '{}'", property, str);
+            return defaultValue;
+        }
+    }
+
+    public JdbcCompiledQueryLease borrowLease() {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Borrowing lease. Leases (before): Active={}, Idle={}", getActiveLeases(), getIdleLeases());
+        }
+        try {
+            return _pool.borrowObject();
+        } catch (Exception e) {
+            throw handleError(e, "borrow lease");
+        }
+    }
+
+    public void returnLease(JdbcCompiledQueryLease lease) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Returning lease. Leases (before): Active={}, Idle={}", getActiveLeases(), getIdleLeases());
+        }
+        try {
+            _pool.returnObject(lease);
+        } catch (Exception e) {
+            throw handleError(e, "return lease");
+        }
+    }
+
+    private RuntimeException handleError(Exception e, String message) {
+        if (logger.isWarnEnabled()) {
+            logger.warn("Unexpected error occurred in compiled JDBC query: " + message, e);
+        }
+
+        if (e instanceof SQLException) {
+            return JdbcUtils.wrapException((SQLException) e, message);
+        } else if (e instanceof RuntimeException) {
+            return (RuntimeException) e;
+        } else {
+            return new MetaModelException(message, e);
+        }
+    }
+
+    protected int getActiveLeases() {
+        return _pool.getNumActive();
+    }
+
+    protected int getIdleLeases() {
+        return _pool.getNumIdle();
+    }
+
+    protected Query getQuery() {
+        return _query;
+    }
+
+    @Override
+    public String toSql() {
+        return _sql;
+    }
+
+    @Override
+    public void close() {
+        logger.debug("Closing compiled JDBC query: {}", _sql);
+        try {
+            _pool.close();
+        } catch (Exception e) {
+            throw handleError(e, "close pool of leases");
+        } finally {
+            _closed = true;
+        }
+    }
+
+    protected List<SelectItem> getSelectItems() {
+        return _query.getSelectClause().getItems();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        if (!_closed) {
+            logger.warn("finalize() invoked, but DataSet is not closed. Invoking close() on {}", this);
+            close();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/63659d33/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQueryLeaseFactory.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQueryLeaseFactory.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQueryLeaseFactory.java
index d4770bb..d973d13 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQueryLeaseFactory.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcCompiledQueryLeaseFactory.java
@@ -29,13 +29,11 @@ import org.apache.commons.pool.PoolableObjectFactory;
  */
 final class JdbcCompiledQueryLeaseFactory implements PoolableObjectFactory<JdbcCompiledQueryLease> {
 
-	private final JdbcDataContext _dataContext;
-    private final Connection _connection;
+    private final JdbcDataContext _dataContext;
     private final String _sql;
 
-    public JdbcCompiledQueryLeaseFactory(JdbcDataContext dataContext, Connection connection, String sql) {
-    	_dataContext = dataContext;
-        _connection = connection;
+    public JdbcCompiledQueryLeaseFactory(JdbcDataContext dataContext, String sql) {
+        _dataContext = dataContext;
         _sql = sql;
     }
     
@@ -43,8 +41,9 @@ final class JdbcCompiledQueryLeaseFactory implements PoolableObjectFactory<JdbcC
     @Override
     public JdbcCompiledQueryLease makeObject() throws Exception {
         try {
-            final PreparedStatement statement = _connection.prepareStatement(_sql);
-            final JdbcCompiledQueryLease lease = new JdbcCompiledQueryLease(_connection, statement);
+            final Connection connection = _dataContext.getConnection();
+            final PreparedStatement statement = connection.prepareStatement(_sql);
+            final JdbcCompiledQueryLease lease = new JdbcCompiledQueryLease(connection, statement);
             return lease;
         } catch (SQLException e) {
             throw JdbcUtils.wrapException(e, "preparing statement");
@@ -53,7 +52,9 @@ final class JdbcCompiledQueryLeaseFactory implements PoolableObjectFactory<JdbcC
 
     @Override
     public void destroyObject(JdbcCompiledQueryLease lease) throws Exception {
-        _dataContext.close(null, null, lease.getStatement());
+        final PreparedStatement statement = lease.getStatement();
+        final Connection connection = lease.getConnection();
+        _dataContext.close(connection, null, statement);
     }
 
     @Override
@@ -68,4 +69,4 @@ final class JdbcCompiledQueryLeaseFactory implements PoolableObjectFactory<JdbcC
     @Override
     public void passivateObject(JdbcCompiledQueryLease obj) throws Exception {
     }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/63659d33/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
index ec309ff..6a4b8a0 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
@@ -64,6 +64,10 @@ public class JdbcDataContext extends AbstractDataContext implements UpdateableDa
 
     public static final String SYSTEM_PROPERTY_BATCH_UPDATES = "metamodel.jdbc.batch.updates";
     public static final String SYSTEM_PROPERTY_CONVERT_LOBS = "metamodel.jdbc.convert.lobs";
+    
+    public static final String SYSTEM_PROPERTY_COMPILED_QUERY_POOL_MAX_SIZE = "metamodel.jdbc.compiledquery.pool.max.size";
+    public static final String SYSTEM_PROPERTY_COMPILED_QUERY_POOL_MIN_EVICTABLE_IDLE_TIME_MILLIS = "metamodel.jdbc.compiledquery.pool.idle.timeout";
+    public static final String SYSTEM_PROPERTY_COMPILED_QUERY_POOL_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "metamodel.jdbc.compiledquery.pool.eviction.period.millis";
 
     public static final String DATABASE_PRODUCT_POSTGRESQL = "PostgreSQL";
     public static final String DATABASE_PRODUCT_MYSQL = "MySQL";

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/63659d33/jdbc/src/test/java/org/apache/metamodel/jdbc/JdbcDataContextTest.java
----------------------------------------------------------------------
diff --git a/jdbc/src/test/java/org/apache/metamodel/jdbc/JdbcDataContextTest.java b/jdbc/src/test/java/org/apache/metamodel/jdbc/JdbcDataContextTest.java
index 7a079cd..1746eaa 100644
--- a/jdbc/src/test/java/org/apache/metamodel/jdbc/JdbcDataContextTest.java
+++ b/jdbc/src/test/java/org/apache/metamodel/jdbc/JdbcDataContextTest.java
@@ -22,17 +22,25 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import javax.sql.DataSource;
 import javax.swing.table.TableModel;
 
 import org.easymock.EasyMock;
+import org.apache.commons.dbcp.BasicDataSource;
 import org.apache.metamodel.DataContext;
 import org.apache.metamodel.MetaModelException;
 import org.apache.metamodel.QueryPostprocessDataContext;
 import org.apache.metamodel.data.DataSet;
 import org.apache.metamodel.data.DataSetTableModel;
+import org.apache.metamodel.data.Row;
 import org.apache.metamodel.jdbc.dialects.DefaultQueryRewriter;
 import org.apache.metamodel.jdbc.dialects.IQueryRewriter;
 import org.apache.metamodel.query.CompiledQuery;
@@ -272,7 +280,7 @@ public class JdbcDataContextTest extends JdbcTestCase {
     public void testMaxRows() throws Exception {
         final Connection realCon = getTestDbConnection();
         final Statement realStatement = realCon.createStatement();
-        
+
         final Connection mockCon = EasyMock.createMock(Connection.class);
         final Statement mockStatement = EasyMock.createMock(Statement.class);
 
@@ -425,4 +433,91 @@ public class JdbcDataContextTest extends JdbcTestCase {
         assertEquals("Row[values=[Australia, 430300.0]]", data2.getRow().toString());
         assertEquals(new DataSetTableModel(data1), new DataSetTableModel(data2));
     }
+
+    public void testReleaseConnectionsInCompiledQuery() throws Exception {
+        final int connectionPoolSize = 2;
+        final int threadCount = 4;
+        final int noOfCallsPerThreads = 30;
+
+        final BasicDataSource ds = new BasicDataSource();
+        ds.setDriverClassName("org.hsqldb.jdbcDriver");
+        ds.setUrl("jdbc:hsqldb:res:metamodel");
+        ds.setInitialSize(connectionPoolSize);
+        ds.setMaxActive(connectionPoolSize);
+        ds.setMaxWait(10000);
+        ds.setMinEvictableIdleTimeMillis(1800000);
+        ds.setMinIdle(0);
+        ds.setMaxIdle(connectionPoolSize);
+        ds.setNumTestsPerEvictionRun(3);
+        ds.setTimeBetweenEvictionRunsMillis(-1);
+        ds.setDefaultTransactionIsolation(java.sql.Connection.TRANSACTION_READ_COMMITTED);
+
+        final JdbcDataContext dataContext = new JdbcDataContext(ds,
+                new TableType[] { TableType.TABLE, TableType.VIEW }, null);
+
+        final JdbcCompiledQuery compiledQuery = (JdbcCompiledQuery) dataContext.query().from("CUSTOMERS")
+                .select("CUSTOMERNAME").where("CUSTOMERNUMBER").eq(new QueryParameter()).compile();
+
+        assertEquals(0, compiledQuery.getActiveLeases());
+        assertEquals(0, compiledQuery.getIdleLeases());
+
+        final String compliedQueryString = compiledQuery.toSql();
+
+        assertEquals(
+                "SELECT _CUSTOMERS_._CUSTOMERNAME_ FROM PUBLIC._CUSTOMERS_ WHERE _CUSTOMERS_._CUSTOMERNUMBER_ = ?",
+                compliedQueryString.replace('\"', '_'));
+
+        assertEquals(0, compiledQuery.getActiveLeases());
+        assertEquals(0, compiledQuery.getIdleLeases());
+
+        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+        final CountDownLatch latch = new CountDownLatch(threadCount);
+        final List<Throwable> errors = new ArrayList<Throwable>();
+        final Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    for (int i = 0; i < noOfCallsPerThreads; i++) {
+                        final DataSet dataSet = dataContext.executeQuery(compiledQuery, new Object[] { 103 });
+                        try {
+                            assertTrue(dataSet.next());
+                            Row row = dataSet.getRow();
+                            assertNotNull(row);
+                            assertEquals("Atelier graphique", row.getValue(0).toString());
+                            assertFalse(dataSet.next());
+                        } finally {
+                            dataSet.close();
+                        }
+                    }
+                } catch (Throwable e) {
+                    errors.add(e);
+                } finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        for (int i = 0; i < threadCount; i++) {
+            executorService.execute(runnable);
+        }
+
+        try {
+            latch.await(60000, TimeUnit.MILLISECONDS);
+
+            if (errors.size() > 0) {
+                throw new IllegalStateException(errors.get(0));
+            }
+            assertTrue(true);
+        } finally {
+            executorService.shutdownNow();
+        }
+
+        assertEquals(0, compiledQuery.getActiveLeases());
+
+        compiledQuery.close();
+
+        assertEquals(0, compiledQuery.getActiveLeases());
+        assertEquals(0, compiledQuery.getIdleLeases());
+    }
+
 }
\ No newline at end of file