РЕШЕНИЕ (см. Ответ ниже).
Я не понял свою проблему в правильном контексте. Реальная проблема заключалась в том, что мой запрос возвращал несколько объектов ResultSet
, и я никогда раньше не сталкивался с этим. Я опубликовал код ниже, который решает проблему.
ПРОБЛЕМА
У меня есть таблица базы данных SQL Server со многими тысячами строк. Моя цель - вытащить данные из исходной базы данных и записать их во вторую базу данных. Из-за ограничений памяти приложения я не смогу сразу вернуть данные. Кроме того, из-за этой конкретной схемы таблиц (над которой я не контролирую), мне не удастся пометить строки, используя какой-то столбец идентификатора.
Джентльмен в администраторах баз данных StackExchange помог мне, собрав что-то, называемое курсором API базы данных, и в основном написал этот сложный запрос, в который мне нужно только отбросить мое утверждение. Когда я запускаю запрос в SQL Management Studio (SSMS), он отлично работает. Я возвращаю все данные за тысячу строк за раз.
К сожалению, когда я пытаюсь перевести это в JDBC-код, я возвращаю только первую тысячу строк.
ВОПРОС
Можно ли использовать JDBC для извлечения курсора API базы данных, вытащить из него первый набор строк, позволить курсору продвигаться, а затем вытаскивать последующие наборы по одному за раз? (В этом случае тысяча строк за раз).
SQL CODE
Это осложняется, поэтому я собираюсь разбить его.
Фактический запрос может быть простым или сложным. Это не имеет значения. Я пробовал несколько разных запросов во время моих экспериментов, и все они работают. Вы просто в основном бросаете его в код SQL в нужном месте. Итак, допустим, что этот простой оператор является нашим запросом:
SELECT MyColumn FROM MyTable;
Фактический курсор API базы данных SQL намного сложнее. Я распечатаю его ниже. Вы можете видеть, что приведенный выше запрос похож на него:
-- 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"));
}
Я не удивлен результатами, но все варианты, которые я пробовал, дают одинаковые результаты.
Из моих исследований кажется, что JDBC делает что-то с курсорами базы данных, когда база данных является Oracle, но вы должны установить тип данных, возвращаемый в результирующем наборе, как объект курсора 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();
}
После некоторого дополнительного поиска в Google я нашел немного кода, опубликованного еще в 2004 году:
http://www.coderanch.com/t/300865/JDBC/databases/SQL-Server-JDBC-Registering-cursor
Джентльмен, который опубликовал фрагмент, который я нашел полезным (Джулиан Кеннеди), предложил: "Прочтите Javadoc для getUpdateCount() и getMoreResults() для четкого понимания". Мне удалось собрать это вместе.
В принципе, я не думаю, что я правильно понял свою проблему с самого начала, чтобы правильно ее сформулировать. Дело в том, что мой запрос будет возвращать данные в нескольких экземплярах ResultSet
. Мне нужен был способ не просто перебирать каждую строку в ResultSet, а скорее перебирать весь набор ResultSets. Это то, что делает код выше.
Если вам нужны все записи из таблицы, просто выберите "Выбрать * из таблицы".
Единственная причина для извлечения в кусках - если есть какое-то промежуточное место для данных: например, если вы показываете его на экране или сохраняете в памяти.
Если вы просто читаете с одного и вставляете в другое, просто читайте все, начиная с первого. Вы не получите никакой лучшей производительности, пытаясь получить в партиях. Если есть разница, это будет отрицательным. Разорвите свой запрос таким образом, чтобы вернуть все. Программное обеспечение 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...