如何从数据库 table 部分获取数据并使用 java 将其放入列表?

how to take data from database table partially and put it to the list using java?

假设我在 oracle table 中有数百万条记录,我想将每条记录放入 java 列表。最后,我想将此列表与另一个列表进行比较。不幸的是,我不能立即执行此操作,因为 table 太大了,我没有足够的内存来存储这么大的列表。我的想法是部分地做到这一点。 例如每次从table中取出500000条记录存入列表,与另一条记录比较后清空。 所以第一次 a 将从数据库中获取 ID 为 1-500000 的记录,然后是 500001-100000 等等。 但问题是如何做到这一点?如果没有这个要求,我会做这样的事情:

String query= "SELECT * FROM myTable";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
// how many columns it the table
int columnsNumber = rsmd.getColumnCount();
List<String> list = new ArrayList<String>();

            while (rs.next()) {
              for(int i=0;i<columnsNumber;i++){

                String row= ""
                String row= row + " "+ rs.getString(i);

            }

 list.add(row);
}

但是如何在 SELECT 查询中使用 WHERE 条件执行此操作 首先对 WHERE id<500000 的查询执行此操作 那么 WHERE id 在 500001 和 100000 之间等等?

您可以在数据库中查询 table 中的总行数,然后使用下一个偏移量创建您的查询,如下所示:

Integer limit = 50000;
Integer offset = 0;
String query= "SELECT COUNT(*) as total FROM myTable";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(query);
Integer total = rs.getInt("total");
Integer itrCount = (int) Math.ceil((double) total/limit);

for(int i=0; i< itrCount; i++){
    String query1 = "SELECT * FROM myTable limit " + limit + " offset " + offset;
    Statement st1 = conn.createStatement();
    ResultSet rs1 = st1.executeQuery(query1);
    ResultSetMetaData rsmd =  rs1.getMetaData();
    int columnsNumber = rsmd.getColumnCount();
    List<String> list = new ArrayList<String>();

    while (rs1.next()) {
      for(int i=0;i<columnsNumber;i++){
            String row= ""
            String row= row + " "+ rs1.getString(i);
       }

       list.add(row);
    }        

    offset += limit;

    // do something with your list
}

首先,我要问。为什么你必须先把它收集到一个 List 中?你能一次比较一个项目吗?由于您建议查看任意子集,这对我来说意味着您正在查看单个项目而不是整个列表。如果是这种情况,那么我建议循环遍历整个 table 并一次处理一条记录。如果不是这种情况,那么我不认为像这样将它分解成块实际上对你有任何好处。

无论如何,我将向您展示几种方法,您可以按照您的要求将其拆分。您或许可以让 ResultSet 接口为您完成这些肮脏的工作。大多数实现会将数据留在数据库中,直到需要时才会将其加载到 JDBC 客户端的内存中。我会先那样试。然后你只需做这样的事情:

int limit = 50000;
String sql = "select * from myTable";
PreparedStatement statement = conn.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
int columnCount = rs.getResultSetMetaData().getColumnCount();
List<String> list = new ArrayList<String>();
while (rs.next()){
  if (list.size() == limit){
     processList(list);
     list = new ArrayList<String>();
  }
  StringBuilder row = new StringBuilder();
  for (int i=1; i <= columnCount; i++){ 
    row.append(rs.getString(i));
  }
  list.add(row.toString());
}
processList(list);

所以这消耗了来自 ResultSet 的 50000 个结果并在继续处理更多记录之前处理它们。

这也可以通过向查询添加一些参数来完成。确切的细节取决于您使用的数据库和版本。 Oracle 12 有一个 fetch firstoffset 子句可以满足您的需要。在此之前,您必须使用rownum。而且你必须小心,否则它可能不会按照你的想法行事。即,您有 运行 一个带有 order by 的查询,将 rownum 放在这些结果上,最后过滤 where 子句中的记录。 SQL 看起来像这样:

select * from
  (select ordered.*, rownum as r
   from (select * from myTable order by ... ) ordered
  ) where r >= 0 and r < 50000

这保证在应用 rownum 之前对行进行排序,并且在过滤之前生成 rownum。如果没有这些步骤,您每次 运行 查询时都可能得到相同的行,否则可能会丢失行。在 Java 中,它看起来像这样:

int batchSize = 50000;
String sql = "select * from "
  + " (select ordered.*, rownum as r "
  + " from (select * from myTable order by ... ) ordered "
  + " ) where r >= ? and r < ? ";
PreparedStatement ps = conn.prepareStatement(sql);
int lowerBound = 0;
boolean keepTryingQuery = true;
while (keepTryingQuery){
  List<String> list = new ArrayList<String>();
  upperBound = lowerBound + batchSize;
  ps.setInt(1, lowerBound);
  ps.setInt(2, upperBound);
  ResultSet rs = ps.executeQuery();
  int columnCount = rs.getResultSetMetaData.getColumnCount();
  while (rs.next()){
    StringBuilder row = new StringBuilder();
    for (int i = 1; i <= columnCount; i++){
      row.append(rs.getString(i));
    }
    list.add(row.toString());
  }      
  processList(list);
  keepTryingQuery = list.size() > 0;
  lowerBound = upperBound;
}

这个 运行 是一个 SQL 声明 return 是 table 的一个子集。然后它将所有这些结果填充到一个列表中。一旦列表已满,它就会做任何需要做的事情。之后,它继续进行查询的下一个范围。它只是不断增加边界,直到查询没有 return 行。真正的关键在于它展示了在 PreparedStatement 上设置参数的机制。您也可以像 Sanju 建议的那样 运行 一个单独的计数查询来显式定义边界。如果 table 上有一个数字主键,这个可以大大简化。然后你可以做类似 select * from myTable where primaryKey >= ? and primaryKey < ? 的事情。您不会在每个批次中获得完全相同的批次大小,但是您根本不必弄乱 rownum。同样,在 Oracle 12 上,您可以执行 select * from myTable order by ... offset ? rows fetch next ? rows 之类的操作来避免处理 rownum.

这两种方法都有一些缺点。对于第一个,我会担心在处理 List 时让 ResultSet 打开。在第二个中,您必须恰到好处地获取详细信息,否则您不会对每一行都只处理一次。在您处理时,两者都容易受到 table 修改的影响。首先,您可能只是为其他人制造了僵局。对于第二个,您将失去实际处理整个 table.

的保证

就我个人而言,我不确定我会使用这两种方法。 我强烈考虑为此使用 Spring 批处理。 它将业务逻辑与 I/O 的机制分开。它还具有内置的 类 可以自动处理许多与此有关的问题。如果那太过分了,我至少会声明更多 类 和接口以将其拆分得更多一些。比较数据的代码不应该知道它来自哪里。生成它的代码不应该知道它的去向。