diff -r e2f406d9cd53 -r 5a24a24d4111 lib/commons-dbcp.jar Binary file lib/commons-dbcp.jar has changed diff -r e2f406d9cd53 -r 5a24a24d4111 src/net/lemnik/eodsql/impl/BaseQueryImpl.java --- a/src/net/lemnik/eodsql/impl/BaseQueryImpl.java Tue Apr 13 07:02:57 2010 +0000 +++ b/src/net/lemnik/eodsql/impl/BaseQueryImpl.java Sat Sep 18 22:46:51 2010 +0200 @@ -8,6 +8,7 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.HashSet; @@ -20,6 +21,7 @@ import javax.sql.DataSource; import net.lemnik.eodsql.BaseQuery; +import net.lemnik.eodsql.EoDException; import net.lemnik.eodsql.QueryTool; import net.lemnik.eodsql.InvalidQueryException; @@ -37,9 +39,7 @@ protected Map methods = new HashMap(); - private ConnectionSource connectionSource; - - private volatile boolean closed = false; + protected ConnectionSource connectionSource; BaseQueryImpl( final ConnectionSource connectionSource, @@ -58,14 +58,15 @@ } protected void close() throws SQLException { - if(!closed) { - closed = true; - connectionSource.close(); - } + connectionSource.close(); } protected boolean isClosed() { - return closed; + try { + return connectionSource.isClosed(); + } catch (SQLException ex) { + throw new EoDException(ex); + } } private Set getParentInterfaces(final Class base) { @@ -166,6 +167,11 @@ } } + protected Context createContext(Annotation annotation, final Object[] args) + { + return new Context(annotation, args); + } + static interface Callable { public Object invoke(Method method, Object[] args) throws Throwable; @@ -208,8 +214,7 @@ final Object[] args) throws Throwable { - final Context context = - new Context(annotation, args); + final Context context = createContext(annotation, args); final Resource connection = new ConnectionSourceConnectionResource(connectionSource); @@ -235,6 +240,8 @@ public void releaseConnection(Connection connection) throws SQLException; public void close() throws SQLException; + + public boolean isClosed() throws SQLException; } @@ -272,6 +279,10 @@ connection.close(); } + public boolean isClosed() throws SQLException { + return connection.isClosed(); + } + } static class DataSourceConnectionSource implements ConnectionSource { @@ -281,23 +292,29 @@ private final DataSource datasource; - private final Set connections = - Collections.synchronizedSet(new HashSet()); + private final boolean autoCommit; + + // Dummy value to associate with an Object in the backing Map + private static final Object PRESENT = new Object(); - DataSourceConnectionSource(final DataSource datasource) { + private final Map connections = Collections.synchronizedMap( + new IdentityHashMap()); + + DataSourceConnectionSource(final DataSource datasource, final boolean autoCommit) { this.datasource = datasource; + this.autoCommit = autoCommit; } @Override protected void finalize() throws Throwable { + close(); super.finalize(); - close(); } public void close() throws SQLException { // when closing, we want exclusive access synchronized(connections) { - final Iterator it = connections.iterator(); + final Iterator it = connections.keySet().iterator(); while(it.hasNext()) { it.next().close(); @@ -306,12 +323,22 @@ } } - public Connection getConnection() throws SQLException { + public boolean isClosed() throws SQLException { + return connections.isEmpty(); + } + + public Connection getConnection() throws SQLException { ConnectionUtil util = CONNECTION_UTILS.get(); if(util == null || util.connection.isClosed()) { final Connection tmp = datasource.getConnection(); - connections.add(tmp); + try { + tmp.setAutoCommit(autoCommit); + } catch(SQLException ex) { + throw new EoDException("Cannot set auto-commit on Connection", ex); + } + + connections.put(tmp, PRESENT); util = new ConnectionUtil(tmp); util.count++; @@ -326,7 +353,7 @@ public void releaseConnection(final Connection connection) throws SQLException { - if(connections.contains(connection)) { + if(connections.containsKey(connection)) { final ConnectionUtil util = CONNECTION_UTILS.get(); if(--util.count <= 0) { @@ -349,6 +376,7 @@ } } + } private static class ConnectionSourceConnectionResource implements Resource { diff -r e2f406d9cd53 -r 5a24a24d4111 src/net/lemnik/eodsql/impl/DefaultQueryFactory.java --- a/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java Tue Apr 13 07:02:57 2010 +0000 +++ b/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java Sat Sep 18 22:46:51 2010 +0200 @@ -8,7 +8,6 @@ import java.sql.Connection; -import java.sql.SQLException; import javax.sql.DataSource; import net.lemnik.eodsql.BaseQuery; @@ -122,21 +121,15 @@ throws InvalidQueryException { if(TransactionQuery.class.isAssignableFrom(query)) { - try { - return constructProxy( - loader, - query, - new TransactionQueryImpl( - dataSource.getConnection(), - query)); - } catch(SQLException ex) { - throw new InvalidQueryException( - "Couldn't get a Connection from the DataSource " + - "for a TransactionQuery", ex); - } + return constructProxy( + loader, + query, + new TransactionQueryImpl( + dataSource, + query)); } else { return construct( - new BaseQueryImpl.DataSourceConnectionSource(dataSource), + new BaseQueryImpl.DataSourceConnectionSource(dataSource, true), query, loader); } diff -r e2f406d9cd53 -r 5a24a24d4111 src/net/lemnik/eodsql/impl/TransactionQueryImpl.java --- a/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java Tue Apr 13 07:02:57 2010 +0000 +++ b/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java Sat Sep 18 22:46:51 2010 +0200 @@ -1,59 +1,97 @@ package net.lemnik.eodsql.impl; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.sql.Savepoint; import java.sql.Connection; import java.sql.SQLException; +import javax.sql.DataSource; + import net.lemnik.eodsql.BaseQuery; -import net.lemnik.eodsql.InvalidQueryException; +import net.lemnik.eodsql.EoDException; import net.lemnik.eodsql.TransactionQuery; +import net.lemnik.eodsql.spi.Context; /** * Created on 2008/07/23 * @author Jason Morris */ class TransactionQueryImpl extends BaseQueryImpl { - protected final Connection connection; + final boolean noAutoClose; public TransactionQueryImpl(final Connection connection, final Class clazz) { super(new SingleConnectionSource(connection), clazz, TransactionQuery.class); - this.connection = connection; + this.noAutoClose = false; addTransactionMethods(); try { connection.setAutoCommit(false); } catch(SQLException ex) { - throw new InvalidQueryException("Cannot set auto-commit on Connection", ex); + throw new EoDException("Cannot set auto-commit on Connection", ex); } } - @Override - protected void close() throws SQLException { - super.close(); + public TransactionQueryImpl(final DataSource datasource, + final Class clazz) { + + super(new DataSourceConnectionSource(datasource, false), clazz, TransactionQuery.class); + this.noAutoClose = true; + addTransactionMethods(); } + @Override + protected Context createContext(Annotation annotation, final Object[] args) + { + final Context context = new Context(annotation, args); + if (noAutoClose) + { + context.setDontCloseConnection(true); + } + return context; + } + protected void commit() throws SQLException { - connection.commit(); + final Connection conn = connectionSource.getConnection(); + conn.commit(); + connectionSource.releaseConnection(conn); } protected void rollback() throws SQLException { - connection.rollback(); + final Connection conn = connectionSource.getConnection(); + conn.rollback(); + connectionSource.releaseConnection(conn); } protected void rollback(Savepoint savepoint) throws SQLException { - connection.rollback(savepoint); + final Connection conn = connectionSource.getConnection(); + conn.rollback(savepoint); + connectionSource.releaseConnection(conn); } protected Savepoint setSavepoint() throws SQLException { - return connection.setSavepoint(); + final Connection conn = connectionSource.getConnection(); + try + { + return conn.setSavepoint(); + } finally + { + connectionSource.releaseConnection(conn); + } } protected Savepoint setSavepoint(String name) throws SQLException { - return connection.setSavepoint(name); + final Connection conn = connectionSource.getConnection(); + try + { + return conn.setSavepoint(name); + } finally + { + connectionSource.releaseConnection(conn); + } } protected void addTransactionMethods() { diff -r e2f406d9cd53 -r 5a24a24d4111 src/net/lemnik/eodsql/spi/Context.java --- a/src/net/lemnik/eodsql/spi/Context.java Tue Apr 13 07:02:57 2010 +0000 +++ b/src/net/lemnik/eodsql/spi/Context.java Sat Sep 18 22:46:51 2010 +0200 @@ -1,14 +1,13 @@ package net.lemnik.eodsql.spi; import java.lang.annotation.Annotation; - +import java.sql.Connection; import java.sql.SQLException; - -import java.util.Map; -import java.util.List; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.ListIterator; +import java.util.Map; import net.lemnik.eodsql.DataSet; @@ -51,6 +50,8 @@ private boolean autoclose = true; + private boolean dontCloseConnection = false; + private boolean closed = false; /** @@ -111,7 +112,10 @@ @Override protected void finalize() throws Exception { - close(); + if (autoclose) + { + close(); + } } /** @@ -231,8 +235,21 @@ return autoclose; } + public boolean isDontCloseConnection() { + return dontCloseConnection; + } + + public void setDontCloseConnection(boolean closeExceptConnection) { + this.dontCloseConnection = closeExceptConnection; + } + + private final boolean isConnectionResource(Resource r) + { + return r.getResourceType() == Connection.class; + } + /** - * Explicidly close this {@code Context}. This method needs to be invoked to close any + * Explicitly close this {@code Context}. This method needs to be invoked to close any * remaining {@code Resource}s that this {@code Context} still holds if the {@code Context} * is not in {@link #setAutoclose(boolean) auto-close} mode. This method is a no-op if the * {@code Context} is already closed. @@ -256,6 +273,10 @@ while(iterator.hasPrevious()) { final Resource r = iterator.previous(); + if (dontCloseConnection && isConnectionResource(r)) + { + continue; + } // close each of the Resource objects if(!r.isClosed()) { diff -r e2f406d9cd53 -r 5a24a24d4111 test/net/lemnik/eodsql/EoDTestCase.java --- a/test/net/lemnik/eodsql/EoDTestCase.java Tue Apr 13 07:02:57 2010 +0000 +++ b/test/net/lemnik/eodsql/EoDTestCase.java Sat Sep 18 22:46:51 2010 +0200 @@ -18,6 +18,10 @@ import java.sql.SQLException; import java.sql.DriverManager; +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; + import junit.framework.TestCase; /** @@ -27,6 +31,7 @@ public abstract class EoDTestCase extends TestCase { private File dir; private Connection connection; + private BasicDataSource dataSource; protected EoDTestCase() { } @@ -106,5 +111,25 @@ return connection; } + + protected DataSource getDataSource(boolean autoCommit) throws ClassNotFoundException + { + if (dataSource == null) + { + dataSource = new BasicDataSource(); + String driverName = System.getProperty("test.db.driver"); + Class.forName(driverName); + dataSource.setDriverClassName(driverName); + + String url = System.getProperty("test.db.url"); + dataSource.setUrl(url); + String user = System.getProperty("test.db.user"); + dataSource.setUsername(user); + String password = System.getProperty("test.db.password"); + dataSource.setPassword(password); + } + dataSource.setDefaultAutoCommit(autoCommit); + return dataSource; + } } diff -r e2f406d9cd53 -r 5a24a24d4111 test/net/lemnik/eodsql/TransactionTest.java --- a/test/net/lemnik/eodsql/TransactionTest.java Tue Apr 13 07:02:57 2010 +0000 +++ b/test/net/lemnik/eodsql/TransactionTest.java Sat Sep 18 22:46:51 2010 +0200 @@ -59,6 +59,41 @@ assertEquals("data set is the wrong length.", 100, objects.size()); objects.close(); } + + public void testTwoCommits() throws Exception { + TransactionTestQuery query = QueryTool.getQuery(getDataSource(false), TransactionTestQuery.class); + + // generate 100 objects into the database + for(int i = 0; i < 100; i++) { + SimpleObject obj = new SimpleObject(); + obj.id = UUID.randomUUID(); + obj.data = Integer.toBinaryString(i); + + query.insert(obj); + } + + query.commit(); + query.close(); + + DataSet objects = query.getObjects(); + assertEquals("data set is the wrong length.", 100, objects.size()); + objects.close(); + + // generate another 100 objects into the database + for(int i = 0; i < 100; i++) { + SimpleObject obj = new SimpleObject(); + obj.id = UUID.randomUUID(); + obj.data = Integer.toBinaryString(100 + i); + + query.insert(obj); + } + + query.commit(); + DataSet objects2 = query.getObjects(); + assertEquals("data set is the wrong length.", 200, objects2.size()); + objects2.close(); + } + // This test fails in Derby!!! public void testRollback() throws Exception { TransactionTestQuery query = QueryTool.getQuery(getConnection(), TransactionTestQuery.class); @@ -84,6 +119,40 @@ objects.close(); } + public void testOneRollbackOneCommit() throws Exception { + TransactionTestQuery query = QueryTool.getQuery(getDataSource(false), TransactionTestQuery.class); + + // generate 100 objects into the database + for(int i = 0; i < 100; i++) { + SimpleObject obj = new SimpleObject(); + obj.id = UUID.randomUUID(); + obj.data = Integer.toBinaryString(i); + + query.insert(obj); + } + + query.rollback(); + query.close(); + + DataSet objects = query.getObjects(); + assertEquals("data set is the wrong length.", 0, objects.size()); + objects.close(); + + // generate another 100 objects into the database + for(int i = 0; i < 100; i++) { + SimpleObject obj = new SimpleObject(); + obj.id = UUID.randomUUID(); + obj.data = Integer.toBinaryString(100 + i); + + query.insert(obj); + } + + query.commit(); + DataSet objects2 = query.getObjects(); + assertEquals("data set is the wrong length.", 100, objects2.size()); + objects2.close(); + } + @Override protected void tearDown() throws Exception { super.tearDown();