Author: wspeirs Date: Mon May 13 19:42:50 2013 New Revision: 1482045 URL: http://svn.apache.org/r1482045 Log: Applied patch from DBUTILS-108 and updated changes.xml
Modified: commons/proper/dbutils/trunk/src/changes/changes.xml commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/QueryRunner.java commons/proper/dbutils/trunk/src/test/java/org/apache/commons/dbutils/QueryRunnerTest.java Modified: commons/proper/dbutils/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/changes/changes.xml?rev=1482045&r1=1482044&r2=1482045&view=diff ============================================================================== --- commons/proper/dbutils/trunk/src/changes/changes.xml (original) +++ commons/proper/dbutils/trunk/src/changes/changes.xml Mon May 13 19:42:50 2013 @@ -44,19 +44,22 @@ The <action> type attribute can be add,u </properties> <body> <release version="1.6" date="201?-??-??" description="Bugfixes and addition of insert methods"> + <action dev="wspeirs" due-to="Micah Huff" type="add" issue="DBUTILS-108"> + Create functionality to return auto-generated keys in batches of SQL inserts + </action> <action dev="simonetripodi" due-to="Moandji Ezana" type="add" issue="DBUTILS-98"> Add missing JavaDoc to QueryRunner#insert </action> <action dev="simonetripodi" type="add" issue="DBUTILS-97"> Add an Abstract ResultSetHandler implementation in order to reduce redundant 'resultSet' variable invocation </action> - <action dev="wspeirs" due-to="Moandji Ezana" type="add" issue="DBUTILS-87"> - Added insert methods to QueryRunner and AsyncQueryRunner that return the generated key. - </action> <action dev="simonetripodi" due-to="yuyf" type="fix" issue="DBUTILS-96"> DbUtils#loadDriver(ClassLoader,String) makes DriverManager throwing "No suitable driver found for jdbc" if ClassLoader is not the System's one </action> + <action dev="wspeirs" due-to="Moandji Ezana" type="add" issue="DBUTILS-87"> + Added insert methods to QueryRunner and AsyncQueryRunner that return the generated key. + </action> </release> <release version="1.5" date="2012-07-20" description="Bugfixes and addition of BeanMapHandler"> <action dev="simonetripodi" due-to="Benedikt Ritter" type="update" issue="DBUTILS-94"> Modified: commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java?rev=1482045&r1=1482044&r2=1482045&view=diff ============================================================================== --- commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java (original) +++ commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AbstractQueryRunner.java Mon May 13 19:42:50 2013 @@ -153,6 +153,34 @@ public abstract class AbstractQueryRunne return conn.prepareStatement(sql); } + + /** + * Factory method that creates and initializes a + * <code>PreparedStatement</code> object for the given SQL. + * <code>QueryRunner</code> methods always call this method to prepare + * statements for them. Subclasses can override this method to provide + * special PreparedStatement configuration if needed. This implementation + * simply calls <code>conn.prepareStatement(sql, returnedKeys)</code> + * which will result in the ability to retrieve the automatically-generated + * keys from an auto_increment column. + * + * @param conn + * The <code>Connection</code> used to create the + * <code>PreparedStatement</code> + * @param sql + * The SQL statement to prepare. + * @param returnedKeys + * Flag indicating whether to return generated keys or not. + * + * @return An initialized <code>PreparedStatement</code>. + * @throws SQLException + * if a database access error occurs + */ + protected PreparedStatement prepareStatement(Connection conn, String sql, int returnedKeys) + throws SQLException { + + return conn.prepareStatement(sql, returnedKeys); + } /** * Factory method that creates and initializes a <code>Connection</code> Modified: commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java?rev=1482045&r1=1482044&r2=1482045&view=diff ============================================================================== --- commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java (original) +++ commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/AsyncQueryRunner.java Mon May 13 19:42:50 2013 @@ -617,4 +617,36 @@ public class AsyncQueryRunner extends Ab }); } + /** + * {@link QueryRunner#insertBatch(String, ResultSetHandler, Object[][])} asynchronously. + * + * @see QueryRunner#insertBatch(String, ResultSetHandler, Object[][]) + * @since 1.6 + */ + public <T> Future<T> insertBatch(final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException { + return executorService.submit(new Callable<T>() { + + @Override + public T call() throws Exception { + return queryRunner.insertBatch(sql, rsh, params); + } + }); + } + + /** + * {@link QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][])} asynchronously. + * + * @see QueryRunner#insertBatch(Connection, String, ResultSetHandler, Object[][]) + * @since 1.6 + */ + public <T> Future<T> insertBatch(final Connection conn, final String sql, final ResultSetHandler<T> rsh, final Object[][] params) throws SQLException { + return executorService.submit(new Callable<T>() { + + @Override + public T call() throws Exception { + return queryRunner.insertBatch(conn, sql, rsh, params); + } + }); + } + } Modified: commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/QueryRunner.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/QueryRunner.java?rev=1482045&r1=1482044&r2=1482045&view=diff ============================================================================== --- commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/QueryRunner.java (original) +++ commons/proper/dbutils/trunk/src/main/java/org/apache/commons/dbutils/QueryRunner.java Mon May 13 19:42:50 2013 @@ -616,4 +616,94 @@ public class QueryRunner extends Abstrac return generatedKeys; } + + /** + * Executes the given batch of INSERT SQL statements. The + * <code>Connection</code> is retrieved from the <code>DataSource</code> + * set in the constructor. This <code>Connection</code> must be in + * auto-commit mode or the insert will not be saved. + * @param <T> The type of object that the handler returns + * @param sql The SQL statement to execute. + * @param rsh The handler used to create the result object from + * the <code>ResultSet</code> of auto-generated keys. + * @param params Initializes the PreparedStatement's IN (i.e. '?') + * @return The result generated by the handler. + * @throws SQLException if a database access error occurs + * @since 1.6 + */ + public <T> T insertBatch(String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException { + return insertBatch(this.prepareConnection(), true, sql, rsh, params); + } + + /** + * Executes the given batch of INSERT SQL statements. + * @param <T> The type of object that the handler returns + * @param conn The connection to use to run the query. + * @param sql The SQL to execute. + * @param rsh The handler used to create the result object from + * the <code>ResultSet</code> of auto-generated keys. + * @param params The query replacement parameters. + * @return The result generated by the handler. + * @throws SQLException if a database access error occurs + * @since 1.6 + */ + public <T> T insertBatch(Connection conn, String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException { + return insertBatch(conn, false, sql, rsh, params); + } + + /** + * Executes the given batch of INSERT SQL statements. + * @param conn The connection to use for the query call. + * @param closeConn True if the connection should be closed, false otherwise. + * @param sql The SQL statement to execute. + * @param rsh The handler used to create the result object from + * the <code>ResultSet</code> of auto-generated keys. + * @param params The query replacement parameters. + * @return The result generated by the handler. + * @throws SQLException If there are database or parameter errors. + * @since 1.6 + */ + private <T> T insertBatch(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException { + if (conn == null) { + throw new SQLException("Null connection"); + } + + if (sql == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null SQL statement"); + } + + if (params == null) { + if (closeConn) { + close(conn); + } + throw new SQLException("Null parameters. If parameters aren't need, pass an empty array."); + } + + PreparedStatement stmt = null; + T generatedKeys = null; + try { + stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS); + + for (int i = 0; i < params.length; i++) { + this.fillStatement(stmt, params[i]); + stmt.addBatch(); + } + stmt.executeBatch(); + ResultSet rs = stmt.getGeneratedKeys(); + generatedKeys = rsh.handle(rs); + + } catch (SQLException e) { + this.rethrow(e, sql, (Object[])params); + } finally { + close(stmt); + if (closeConn) { + close(conn); + } + } + + return generatedKeys; + } } Modified: commons/proper/dbutils/trunk/src/test/java/org/apache/commons/dbutils/QueryRunnerTest.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/test/java/org/apache/commons/dbutils/QueryRunnerTest.java?rev=1482045&r1=1482044&r2=1482045&view=diff ============================================================================== --- commons/proper/dbutils/trunk/src/test/java/org/apache/commons/dbutils/QueryRunnerTest.java (original) +++ commons/proper/dbutils/trunk/src/test/java/org/apache/commons/dbutils/QueryRunnerTest.java Mon May 13 19:42:50 2013 @@ -30,8 +30,11 @@ import java.sql.Connection; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import javax.sql.DataSource; @@ -53,6 +56,7 @@ public class QueryRunnerTest { @Mock PreparedStatement stmt; @Mock ParameterMetaData meta; @Mock ResultSet results; + @Mock ResultSetMetaData resultsMeta; @Before public void setUp() throws Exception { @@ -416,6 +420,47 @@ public class QueryRunnerTest { Assert.assertEquals(1L, generatedKey.longValue()); } + + @Test + public void testGoodBatchInsert() throws Exception { + results = mock(ResultSet.class); + resultsMeta = mock(ResultSetMetaData.class); + + when(meta.getParameterCount()).thenReturn(2); + when(conn.prepareStatement(any(String.class), eq(Statement.RETURN_GENERATED_KEYS))).thenReturn(stmt); + when(stmt.getGeneratedKeys()).thenReturn(results); + when(results.next()).thenReturn(true).thenReturn(true).thenReturn(false); + when(results.getMetaData()).thenReturn(resultsMeta); + when(resultsMeta.getColumnCount()).thenReturn(1); + + ResultSetHandler<List<Object>> handler = new ResultSetHandler<List<Object>>() + { + public List<Object> handle(ResultSet rs) throws SQLException + { + List<Object> objects = new ArrayList<Object>(); + while (rs.next()) + { + objects.add(new Object()); + } + return objects; + } + }; + + Object[][] params = new Object[2][2]; + params[0][0] = "Test"; + params[0][1] = "Blah"; + params[1][0] = "Test2"; + params[1][1] = "Blah2"; + + List<Object> generatedKeys = runner.insertBatch("INSERT INTO blah(col1, col2) VALUES(?,?)", handler, params); + + verify(stmt, times(2)).addBatch(); + verify(stmt, times(1)).executeBatch(); + verify(stmt, times(1)).close(); // make sure we closed the statement + verify(conn, times(1)).close(); // make sure we closed the connection + + Assert.assertEquals(2, generatedKeys.size()); + } // helper method for calling batch when an exception is expected private void callUpdateWithException(Object... params) throws Exception {