当前位置: 首页 > 知识库问答 >
问题:

使用Java流使用数据库游标

鲍鸿波
2023-03-14

我想使用Java流使用数据库游标。我希望Java流能够根据需要获取和处理行,并避免先在内存中加载所有500万行,然后再进行处理。

是否可以在不将整个表加载到RAM中的情况下使用它?

到目前为止,我的代码如下所示:

Cursor<Product> products = DAO.selectCursor(...);

// 1. Initialize variables
long count = 0;
...
for (Iterator<Product> it = products.iterator(); it.hasNext();) {
  Product p = it.next();
  // 2. Processing each row
  ...
}
// 3. Concluding (processing totals, stats, etc.)
double avg = total / count;
...

它确实工作得很好,但是有点麻烦,我想利用流应用编程接口。

共有1个答案

文英达
2023-03-14

首先,我们必须讨论如何从数据库中获取数据。如果您的目的是浏览大量记录,而不希望在内存中同时加载所有记录,则有两种选择:

  1. 把结果分页。
  2. 让你的司机把结果分页。

如果您已经有了一个基于游标的迭代器,可以根据需要检索分页数据,那么您可以使用JDK API中的拆分器流支持实用程序类将其转换为

Stream<Product> products = StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(cursor.iterator(),
                        Spliterator.NONNULL |
                                Spliterator.ORDERED |
                                Spliterator.IMMUTABLE), false)

否则,你将不得不建立自己的东西。

如果JDBC驱动程序支持fetch size属性,则可以执行以下操作:

Connection con = ds.getConnection();
con.setAutoCommit(false);
PreparedStatement stm = con.prepareStatement("SELECT order_number FROM orders WHERE order_date >= '2018-08-12'", ResultSet.TYPE_FORWARD_ONLY);
stm.setFetchSize(1000);
ResultSet rs = stm.executeQuery();

此时,rs包含1000条记录的第一次读取,直到您阅读了上一页,它才会从数据库中检索更多记录。

所有这一切的棘手之处在于,在读取完所有记录之前,您无法关闭任何资源(即连接、预处理语句和结果集),并且由于我们要构建的流在默认情况下是惰性的,这意味着我们必须保持所有这些资源处于打开状态,直到处理完该流。

也许最简单的方法是围绕此逻辑构建一个迭代器,当迭代器实际到达所有数据的末尾时,您可以关闭所有资源(即!rs.next()),或者另一种方法是在流关闭时完成所有工作(stream.onClose())。

一旦有了迭代器,使用JDK API中的SpliteratorsStreamSupport实用程序类从迭代器中构建流就非常简单了。

我的基本实现看起来有点像这样。这只是为了说明的目的。你可能想给你的特殊情况更多的爱。

public Stream<String> getUsers() {
    DataSource ds = jdbcTemplate.getDataSource();
    try {
        Connection conn = ds.getConnection();
        conn.setAutoCommit(false);
        PreparedStatement stm = conn.prepareStatement("SELECT id FROM users", ResultSet.TYPE_FORWARD_ONLY);
        //fetch size is what guarantees only 1000 records at the time
        stm.setFetchSize(1000);
        ResultSet rs = stm.executeQuery();

        Iterator<String> sqlIter = new Iterator<>() {
            @Override
            public boolean hasNext() {
                try {
                    return rs.next();
                } catch (SQLException e) {
                    closeResources(conn, stm, rs);
                    throw new RuntimeException("Failed to read record from ResultSet", e);
                }
            }

            @Override
            public String next() {
                try {
                    return rs.getString("id");
                } catch (SQLException e) {
                    closeResources(conn, stm, rs);
                    throw new RuntimeException("Failed to read record from ResultSet", e);
                }
            }
        };

        //turn iterator into a stream
        return StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(sqlIter,
                        Spliterator.NONNULL |
                                Spliterator.ORDERED |
                                Spliterator.IMMUTABLE), false
        ).onClose(() -> {
            //make sure to close resources when done with the stream
            closeResources(conn, stm, rs);
        });


    } catch (SQLException e) {
        logger.error("Failed to process data", e);
        throw new RuntimeException(e);
    }
}

private void closeResources(Connection conn, PreparedStatement ps, ResultSet rs) {
    try (conn; ps; rs) {
        logger.info("Resources successfully closed");
    } catch (SQLException e) {
        logger.warn("Failed to properly close database sources", e);
    }
}

这里的关键点是要注意,我们返回的流应该运行一些onClose逻辑,因此当我们使用流时,必须确保执行流。close()当我们完成它时,以确保我们关闭到目前为止保持活动状态的所有资源(即connstmrs)。

最好的方法也许是使用资源的try,这样try将负责关闭流。

try(Stream<String> users = userRepo.getUsers()){
    //print users to the main output retrieving 1K at the time
    users.forEach(System.out::println);
}

另一种方法是您自己对结果进行分页,这取决于数据库,但使用诸如limit和offset之类的select子句,您可以请求特定的记录页,处理它们,然后检索更多的记录。

select id from users LIMIT 1000 OFFSET 5

在这种情况下,迭代器将消耗所有页面,完成后,请求下一页,直到在最后一页中没有找到更多记录。

这种另一种方法的优点是资源可以在迭代器本身中立即控制。

我不会开发一个这样的例子,留给你去尝试。

 类似资料:
  • 本文向大家介绍mongodb数据库游标的使用浅析,包括了mongodb数据库游标的使用浅析的使用技巧和注意事项,需要的朋友参考一下 mongodb中的游标使用示例如下:   假设执行如下操作: 使用find()返回一个游标: 使用游标的forEach()循环遍历:

  • 本章节将介绍如何创建一个从数据表 country 中读取国家数据并显示出来的页面。 为了实现这个目标,你将会配置一个数据库连接, 创建一个活动记录类, 并且创建一个操作及一个视图。 贯穿整个章节,你将会学到: 配置一个数据库连接 定义一个活动记录类 使用活动记录从数据库中查询数据 以分页方式在视图中显示数据 请注意,为了掌握本章你应该具备最基本的数据库知识和使用经验。 尤其是应该知道如何创建数据库

  • 使用 JDBC 连接数据库 JAVA应用要连接到数据库,首先需要加载数据库驱动,然后获得一个数据库连接,下面是一个简单的例子: import java.sql.*; public class Test { public static void main(String[] a) throws Exception { Class.forName("org

  • 本文向大家介绍JAVA使用DBUtils操作数据库,包括了JAVA使用DBUtils操作数据库的使用技巧和注意事项,需要的朋友参考一下 摘要:本文主要学习了如何使用DBUtils在Java代码中更方便的操作数据库。 概述 DBUtils是Java编程中的数据库操作实用工具,小巧简单实用。 DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。 使用 准备 如果需要使用DBUtil

  • SQLAlchemy 1.4 / 2.0 Tutorial 此页是 SQLAlchemy 1.4/2.0教程 . 上一页: 处理事务和DBAPI |下一步: |next| 使用数据库元数据 随着引擎和SQL执行的停止,我们准备开始一些炼金术。SQLAlchemy Core和ORM的核心元素是SQL表达式语言,它允许流畅、可组合地构造SQL查询。这些查询的基础是表示数据库概念(如表和列)的Pytho

  • 数据库:Oracle 11gr2Web应用:JSP(Java)我有两个表: IdCity_FK引用来自“城市”的列 Id。 问题:如何从具有以下字段的插入.jsp页面插入数据:人名,城市。 我希望用户输入自己的名字,并从动态生成的列表框中选择一个城市(使用游标Oracle或其他方法)。< br >例如,不输入:Andrew,12,而是输入Andrew并选择New York。我使用Oracle RE