使用带有JDBC和SQLServer的数据库API游标来选择批处理结果

已解决(见下面的答案。)

我在正确的背景下不理解我的问题。 真正的问题是我的查询返回了多个ResultSet对象,我之前从未遇到过这个问题。 我在下面发布了解决问题的代码。


问题

我有一个包含数千行的SQL Server数据库表。 我的目标是从源数据库中提取数据并将其写入第二个数据库。 由于应用程序内存限制,我无法一次性撤回数据。 此外,由于这个特定的表的模式(我无法控制),我没有办法使用某种ID列来勾选行。

数据库管理员StackExchange的一位绅士通过组合一个称为数据库API游标的东西来帮助我,并且基本上编写了这个复杂的查询,我只需要将我的语句放入其中。 当我在SQL Management Studio(SSMS)中运行查询时,它运行良好。 我得到了所有数据,一次一千行。

不幸的是,当我尝试将其转换为JDBC代码时,我只返回前千行。

是否可以使用JDBC检索数据库API游标,从中提取第一组行,允许游标前进,然后一次拉出一个后续集合? (在这种情况下,一次一千行。)

SQL代码

这变得复杂,所以我要打破它。

实际查询可以是简单的也可以是复杂的。 没关系。 我在实验过程中尝试了几种不同的查询,它们都有效。 您只需将其放入适当位置的SQL代码中。 所以,让我们把这个简单的陈述作为我们的查询:

 SELECT MyColumn FROM MyTable; 

实际的SQL数据库API游标要复杂得多。 我将在下面打印出来。 您可以在其中看到上面的查询:

 -- http://dba.stackexchange.com/a/82806 DECLARE @cur INTEGER , -- FAST_FORWARD | AUTO_FETCH | AUTO_CLOSE @scrollopt INTEGER = 16 | 8192 | 16384 , -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE @ccopt INTEGER = 1 | 32768 | 65536 ,@rowcount INTEGER = 1000 ,@rc INTEGER; -- Open the cursor and return the first 1,000 rows EXECUTE @rc = sys.sp_cursoropen @cur OUTPUT ,'SELECT MyColumn FROM MyTable' ,@scrollopt OUTPUT ,@ccopt OUTPUT ,@rowcount OUTPUT; IF @rc  16 -- FastForward cursor automatically closed BEGIN -- Name the cursor so we can use CURSOR_STATUS EXECUTE sys.sp_cursoroption @cur ,2 ,'MyCursorName'; -- Until the cursor auto-closes WHILE CURSOR_STATUS('global', 'MyCursorName') = 1 BEGIN EXECUTE sys.sp_cursorfetch @cur ,2 ,0 ,1000; END; END; 

正如我所说,上面在数据库中创建了一个游标,并要求数据库执行该语句,跟踪(内部)它返回的数据,并一次返回数千行。 它很棒。

JDBC代码

这就是我遇到问题的地方。 我的Java代码没有编译问题或运行时问题。 我遇到的问题是它只返回前千行。 我不明白如何正确使用数据库游标。 我尝试过Java基础知识的变体:

 // Hoping to get all of the data, but I only get the first thousand. ResultSet rs = stmt.executeQuery(fq.getQuery()); while (rs.next()) { System.out.println(rs.getString("MyColumn")); } 

我对结果并不感到惊讶,但我尝试的所有变化产生了相同的结果。

从我的研究中看,当数据库是Oracle时,JDBC会对数据库游标执行某些操作,但您必须将结果集中返回的数据类型设置为Oracle游标对象。 我猜测有类似SQL Server的东西,但我还是找不到任何东西。

有谁知道一种方式?

我完整地包含了示例Java代码(尽管很丑陋)。

 // FancyQuery.java import java.sql.*; public class FancyQuery { // Adapted from http://dba.stackexchange.com/a/82806 String query = "DECLARE @cur INTEGER\n" + " ,\n" + " -- FAST_FORWARD | AUTO_FETCH | AUTO_CLOSE\n" + " @scrollopt INTEGER = 16 | 8192 | 16384\n" + " ,\n" + " -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE\n" + " @ccopt INTEGER = 1 | 32768 | 65536\n" + " ,@rowcount INTEGER = 1000\n" + " ,@rc INTEGER;\n" + "\n" + "-- Open the cursor and return the first 1,000 rows\n" + "EXECUTE @rc = sys.sp_cursoropen @cur OUTPUT\n" + " ,'SELECT MyColumn FROM MyTable;'\n" + " ,@scrollopt OUTPUT\n" + " ,@ccopt OUTPUT\n" + " ,@rowcount OUTPUT;\n" + " \n" + "IF @rc  16 -- FastForward cursor automatically closed\n" + "BEGIN\n" + " -- Name the cursor so we can use CURSOR_STATUS\n" + " EXECUTE sys.sp_cursoroption @cur\n" + " ,2\n" + " ,'MyCursorName';\n" + "\n" + " -- Until the cursor auto-closes\n" + " WHILE CURSOR_STATUS('global', 'MyCursorName') = 1\n" + " BEGIN\n" + " EXECUTE sys.sp_cursorfetch @cur\n" + " ,2\n" + " ,0\n" + " ,1000;\n" + " END;\n" + "END;\n"; public String getQuery() { return this.query; } public static void main(String[ ] args) throws Exception { String dbUrl = "jdbc:sqlserver://tc-sqlserver:1433;database=MyBigDatabase"; String user = "mario"; String password = "p@ssw0rd"; String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; FancyQuery fq = new FancyQuery(); Class.forName(driver); Connection conn = DriverManager.getConnection(dbUrl, user, password); Statement stmt = conn.createStatement(); // We expect to get 1,000 rows at a time. ResultSet rs = stmt.executeQuery(fq.getQuery()); while (rs.next()) { System.out.println(rs.getString("MyColumn")); } // Alas, we've only gotten 1,000 rows, total. rs.close(); stmt.close(); conn.close(); } } 

我想到了。

 stmt.execute(fq.getQuery()); ResultSet rs = null; for (;;) { rs = stmt.getResultSet(); while (rs.next()) { System.out.println(rs.getString("MyColumn")); } if ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1)) { break; } } if (rs != null) { rs.close(); } 

经过一些额外的谷歌搜索后,我在2004年发现了一些代码:

http://www.coderanch.com/t/300865/JDBC/databases/SQL-Server-JDBC-Registering-cursor

发布我发现有帮助的片段的绅士(Julian Kennedy)建议:“阅读Javadoc for getUpdateCount()和getMoreResults()以获得清晰的理解。” 我能够把它拼凑起来。

基本上,我不认为我在一开始就很好地理解我的问题,以便正确地说出来。 它归结为我的查询将返回多个ResultSet实例中的数据。 我需要的是一种方法,不仅可以遍历ResultSet中的每一行,而是遍历整个ResultSet集。 这就是上面的代码所做的。

如果您想要表中的所有记录,只需执行“从表中选择*”即可。

检索块的唯一原因是数据是否存在中间位置:例如,如果您在屏幕上显示它,或将其存储在内存中。

如果您只是从一个读取并插入另一个,只需从第一个读取所有内容。通过尝试批量检索,您将无法获得更好的性能。 如果存在差异,则为负数。 以一种带回所有内容的方式构建您的查询。 JDBC软件将处理您需要的所有其他分解和重构。

但是,您应该批量更新/插入事物。

该设置将在两个连接上创建两个语句:

 Statement stmt = null; ResultSet rs = null; PreparedStatement insStmt = null; stmt = conDb1.createStatement(); insStmt = conDb2.prepareStament("insert into tgt_db2_table (?,?,?,?,?......etc. ?,?) "); rs = stmt.executeQuery("select * from src_db1_table"); 

然后,正常循环选择,但在目标上使用批处理。

  int batchedRecordCount = 0; while (rs.next()) { System.out.println(rs.getString("MyColumn")); //Here you read values from the cursor and set them to the insStmt ... String field1 = rs.getString(1); String field2 = rs.getString(2); int field3 = rs.getInt(3); //--- etc. insStmt.setString(1, field1); insStmt.setString(2, field2); insStmt.setInt(3, field3); //----- etc. for all the fields batchedRecordCount++; insStmt.addBatch(); if (batchRecordCount > 1000) { insStmt.executeBatch(); } } if (batchRecordCount > 0) { //Finish of the final (partial) set of records insStmt.executeBatch(); } //Close resources...