Java Stream API实践:避免共享可变性实现分批数据查询

java stream api实践:避免共享可变性实现分批数据查询

本文探讨了在Java中处理分批数据库查询时如何避免共享可变性问题。通过利用Java Stream API的map和flatMap操作,可以以函数式、不可变的方式收集数据,从而提升代码的线程安全性和可读性,尤其适用于需要将大列表拆分为小批次进行处理的场景。

引言:共享可变性带来的挑战

在实际的软件开发中,我们经常会遇到需要从数据库中批量获取数据的场景。然而,数据库通常会对单次查询的参数数量有所限制(例如,SQL IN子句的参数数量上限)。这意味着当我们需要查询大量ID对应的数据时,必须将这些ID列表进行分批处理,然后多次执行查询。

考虑以下Java代码示例,它试图解决分批查询的问题:

AtomicInteger counter = new AtomicInteger();List catList = new ArrayList();List dogList = new ArrayList();List numbers = Stream.iterate(1, e -> e + 1)    .limit(5000)    .collect(Collectors.toList());// 将大列表分割成大小为500的小批次Collection<List> partitionedListOfNumbers = numbers.stream()    .collect(Collectors.groupingBy(num -> counter.getAndIncrement() / 500))    .values();// 遍历每个批次并累加结果partitionedListOfNumbers.stream()    .forEach(list -> {        List interimCatList = catRepo.fetchCats(list); // 从数据库获取Cat        catList.addAll(interimCatList); // 修改外部的catList        List interimDogList = dogRepo.fetchDogs(list); // 从数据库获取Dog        dogList.addAll(interimDogList); // 修改外部的dogList    });

上述代码虽然实现了分批查询的功能,但存在一个关键问题:共享可变性(Shared Mutability)。在forEach循环内部,catList和dogList这两个外部列表被反复修改(通过addAll操作)。这种模式在单线程环境下可能不明显,但在多线程或并发环境中,可能会导致数据不一致、竞态条件等难以调试的错误。函数式编程鼓励使用不可变数据和无副作用的操作,以提高代码的健壮性和可预测性。

Java Stream API:函数式编程的解决方案

Java 8引入的Stream API提供了一种声明式、函数式的方式来处理集合数据,它强调数据流的转换而非直接修改。通过利用Stream API的map和flatMap操作,我们可以重构上述代码,彻底避免共享可变性问题。

立即学习“Java免费学习笔记(深入)”;

核心思想是:让每个批次查询操作返回其自身的结果列表,然后将所有这些结果列表“展平”并收集到一个全新的、不可变的结果列表中。

重构步骤与代码示例

为了更好地演示,我们首先定义一些辅助类和接口:

import java.util.*;import java.util.concurrent.atomic.AtomicInteger;import java.util.function.Function;import java.util.stream.Collectors;import java.util.stream.IntStream;import java.util.stream.Stream;// 模拟数据库查询接口interface CatRepo {    List fetchCats(List keys);}interface DogRepo {    List fetchDogs(List keys);}// 示例实体类class Cat {    int id;    String name;    public Cat(int id) { this.id = id; this.name = "Cat-" + id; }    @Override public String toString() { return "Cat{id=" + id + ", name='" + name + "'}"; }}class Dog {    int id;    String name;    public Dog(int id) { this.id = id; this.name = "Dog-" + id; }    @Override public String toString() { return "Dog{id=" + id + ", name='" + name + "'}"; }}// 模拟数据库查询实现class MockCatRepo implements CatRepo {    @Override    public List fetchCats(List keys) {        // 模拟数据库延迟        // try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }        return keys.stream().map(Cat::new).collect(Collectors.toList());    }}class MockDogRepo implements DogRepo {    @Override    public List fetchDogs(List keys) {        // 模拟数据库延迟        // try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }        return keys.stream().map(Dog::new).collect(Collectors.toList());    }}

现在,我们使用Stream API来重构核心逻辑。

1. 生成并分批处理键列表

首先,我们需要生成一个包含所有待查询ID的列表,并将其分割成多个小批次。这里我们使用IntStream.rangeClosed生成整数序列,并结合Collectors.groupingBy进行分批。

// 初始化模拟仓库CatRepo catRepo = new MockCatRepo();DogRepo dogRepo = new MockDogRepo();AtomicInteger counter = new AtomicInteger();int BATCH_SIZE = 500;int TOTAL_NUMBERS = 5000; // 模拟5000个ID// 生成1到5000的整数,并按BATCH_SIZE进行分批Collection<List> partitionedListOfNumbers = IntStream.rangeClosed(1, TOTAL_NUMBERS)    .boxed() // 将IntStream转换为Stream    .collect(Collectors.groupingBy(num -> counter.getAndIncrement() / BATCH_SIZE))    .values(); // 获取所有分批后的子列表集合

在这个步骤中,AtomicInteger在这里的作用是为每个元素生成一个递增的索引,然后通过index / BATCH_SIZE将元素分配到不同的组中,从而实现批次划分。

2. 使用map和flatMap进行数据获取与合并

现在,我们可以利用Stream API的强大功能来并行或顺序地处理这些批次,并以不可变的方式收集结果。

map操作: 对partitionedListOfNumbers中的每一个List(即一个批次)调用fetchCats或fetchDogs方法。map操作会返回一个Stream<List>或Stream<List>,即一个包含多个列表的流。flatMap操作: flatMap用于将Stream<List>展平为Stream。它会将流中的每个内部列表展开,并将其所有元素合并到一个新的单一流中。collect(Collectors.toList()): 最后,将展平后的流中的所有元素收集到一个新的List中。这个新的列表是不可变的,因为它是在所有操作完成后一次性构建的。

// 获取Cat列表List catList = partitionedListOfNumbers.stream()    .map(catRepo::fetchCats)      // 对每个批次调用fetchCats,得到 Stream<List>    .flatMap(Collection::stream)  // 将 Stream<List> 展平为 Stream    .collect(Collectors.toList());// 收集到最终的 List// 获取Dog列表List dogList = partitionedListOfNumbers.stream()    .map(dogRepo::fetchDogs)      // 对每个批次调用fetchDogs,得到 Stream<List>    .flatMap(Collection::stream)  // 将 Stream<List> 展平为 Stream    .collect(Collectors.toList());// 收集到最终的 List// 打印结果(可选)// System.out.println("Fetched Cats: " + catList.size());// System.out.println("Fetched Dogs: " + dogList.size());// System.out.println("First Cat: " + (catList.isEmpty() ? "N/A" : catList.get(0)));// System.out.println("Last Cat: " + (catList.isEmpty() ? "N/A" : catList.get(catList.size() - 1)));

通过这种方式,我们完全避免了对外部可变列表的直接修改。catList和dogList在声明时即被初始化,并在所有数据处理完成后,通过collect操作一次性赋值,确保了其不可变性。

优化:抽象通用查询逻辑

观察上述代码,catList和dogList的生成逻辑高度相似,唯一的区别在于调用的fetch方法。我们可以进一步抽象出一个通用方法来减少代码重复:

/** * 通用方法:根据分批的键列表和查询函数,批量获取数据并合并。 * @param partitionedKeys 分批的键列表 * @param fetchFunction 每个批次对应的查询函数(例如:catRepo::fetchCats) * @param  返回结果的类型 * @return 包含所有查询结果的合并列表 */public static  List fetchAnimalsInBatches(        Collection<List> partitionedKeys,        Function<List, List> fetchFunction) {    return partitionedKeys.stream()        .map(fetchFunction)          // 应用传入的查询函数        .flatMap(Collection::stream) // 展平结果        .collect(Collectors.toList());// 收集到最终列表}// 如何使用这个通用方法List catListOptimized = fetchAnimalsInBatches(partitionedListOfNumbers, catRepo::fetchCats);List dogListOptimized = fetchAnimalsInBatches(partitionedListOfNumbers, dogRepo::fetchDogs);// System.out.println("Optimized Fetched Cats: " + catListOptimized.size());// System.out.println("Optimized Fetched Dogs: " + dogListOptimized.size());

这个通用方法极大地提高了代码的复用性和可维护性,使得我们可以用更简洁的方式处理不同类型的数据查询。

注意事项与最佳实践

不可变性与线程安全: 这种基于Stream API的方案天然地避免了共享可变性,使得代码在多线程环境下更加安全,不易出现竞态条件。资源管理: Stream API本身不直接管理数据库连接等外部资源。fetchCats和fetchDogs内部的数据库操作仍需遵循标准的资源管理(如使用try-with-resources)。错误处理: 数据库查询方法(如fetchCats)内部应包含适当的错误处理逻辑。如果查询可能抛出受检异常,map操作需要进行相应的处理(例如,通过包装成RuntimeException或使用Try monad等)。性能考量: 对于极大规模的数据集,Stream操作可能引入一定的内存或CPU开销。但对于常见的数据库分批查询场景,其性能通常是可接受且高效的。如果需要最大化性能,可以考虑使用parallelStream(),但这会增加并发复杂性,并需要确保fetchFunction是线程安全的。分批策略: 示例中使用AtomicInteger和groupingBy进行分批,这是一种有效的手段。此外,也可以使用如Guava库中的Lists.partition方法来更简洁地实现列表分批。可读性: 函数式编程风格的代码通常更简洁、意图更明确,提高了代码的可读性。

总结

通过本教程,我们学习了如何利用Java Stream API的map和flatMap操作,以函数式、不可变的方式解决分批数据库查询中的共享可变性问题。这种方法不仅提升了代码的线程安全性,还使得代码更加简洁、可读性更强。在处理集合数据时,拥抱Stream API和函数式编程范式,能够帮助我们编写出更健壮、更易于维护的Java应用程序。

以上就是Java Stream API实践:避免共享可变性实现分批数据查询的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/39400.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月6日 02:15:53
下一篇 2025年11月6日 02:23:31

相关推荐

  • Web3.0是啥?Web3.0和区块链啥关系?

    在探讨互联网形态演进的语境中,web3.0这一概念日益受到关注。它通常被描述为一个新的网络范式,旨在解决当前互联网结构中的一些固有问题。理解web3.0,需要审视其核心理念以及支撑其运作的关键技术。 Web3.0概念解析 描述Web3.0所倡导的理念:Web3.0代表了一种互联网架构的演进,与早期以…

    2025年12月8日
    000
  • 区块链DEX是什么?DEX和CEX有什么区别?

    binance币安交易所 注册入口: APP下载: 欧易OKX交易所 注册入口: APP下载: 火币交易所: 注册入口: APP下载: 区块链技术的出现,不仅带来了数字货币,还催生了新的交易场所形式。去中心化交易所(DEX)与中心化交易所(CEX)是目前加密资产领域中最主要的两种交易平台类型,它们在…

    2025年12月8日
    000
  • 区块链DApp是什么?DApp和APP有啥区别?

    区块链技术的兴起带来了分布式账本的概念,并在此基础上催生了一种全新的应用形态:去中心化应用(dapp)。这些dapp与我们日常生活中广泛使用的传统应用程序(app)在核心设计和运行机制上存在本质区别。理解dapp的定义及其与app的差异,对于认识分布式技术的影响至关重要。 区块链DApp的核心概念 …

    2025年12月8日
    000
  • NFT有什么用?NFT就是一张图片吗?

    nft的全称是非同质化代币。理解nft需要区分数字文件本身与链上记录的所有权或权益证明。很多人看到一些nft项目是数字艺术品,便认为nft仅仅是那张可以被复制的图片,这种看法并不完全准确。 一个NFT实际上是一个记录在区块链上的独特代币。这个代币是唯一的,不可分割,并且包含了关于它所代表的资产的信息…

    2025年12月8日
    000
  • 质押和锁仓一样吗?质押能随时取消吗?

    在某些数字资产或区块链网络的设计框架内,”质押“(staking)是一个核心概念。它通常与基于权益证明(proof-of-stake, pos)机制的区块链有关。质押的本质是用户将自己的数字资产锁定在网络中,以此作为参与网络维护、验证交易和生成新区块的经济承诺。 1、这种机制…

    2025年12月8日
    000
  • 一文详解欧盟、阿联酋、新加坡三地稳定币监管框架

    本文对稳定币的监管框架的分析主要将从以下几个角度展开:监管进程、规范文件、监管部门以及监管框架的核心内容,具体内容框架如下: 目录 (一)欧盟 1、监管进程和规范文件 2、对应监管部门 3、监管框架主要内容 a.稳定币的定义 b.发行人的准入门槛 c.币值稳定机制和储备资产的维持 d.流通环节的合规…

    2025年12月8日 好文分享
    000
  • 殴易OKX如何设置合约模式(开平仓模式、买卖模式),图文教程手机app端

    殴易OKX如何设置合约模式(开平仓模式、买卖模式),图文教程手机app端 合约仓位模式的分类:如何设置合约仓位模式,合约仓位模式分为双向持仓模式和单向持仓模式,该设置对所有交易品种生效。持仓或挂单时的限制,若存在持仓或挂单,则不支持调整仓位模式。 双向持仓模式和单向持仓模式区别: 双向持仓模式:指投…

    2025年12月8日 好文分享
    000
  • 什么是 ApeCoin?APE币价格预测:能达到100美元吗?

    apecoin 在 web3 世界中仍然备受关注,交易者和投资者依旧好奇它的未来走向。其强大而活跃的社区使其始终保持热度,成为去中心化世界中的一股稳定力量。 ApeCoin 能涨到 100 美元吗——是幻想还是现实?我们一起来深入探讨!我们将讨论 ApeCoin 的潜在涨幅、可能触发大幅上涨的因素,…

    2025年12月8日
    000
  • 比特币和以太坊交易平台有哪些?哪些软件可以炒币

    进入加密货币的世界,无论是比特币还是以太坊,选择一个可靠的交易平台是迈出成功交易第一步的关键。面对市场上琳琅满目的选择,找到一个既安全、易用,又提供丰富交易功能的平台,对于新手和经验丰富的交易者都至关重要。一个优秀的交易平台不仅能提供流畅的交易体验,还能确保您的资产安全,并提供分析工具和学习资源,帮…

    2025年12月8日 好文分享
    000
  • 币安与gate.io交易所全面对比 更适合新手

    %ignore_a_1%平台是数字资产交易的核心场所。对于刚接触加密领域的用户来说,选择一个合适的交易平台至关重要,它关系到交易体验、资产安全和学习成本。全球范围内有众多加密货币交易所,其中币安(binance)和gate.io是广受关注的两家。它们各自拥有庞大的用户群体和独特的服务特点。了解它们的…

    2025年12月8日
    000
  • $ 1 $ 1的加密支持者:2025年的Meme Coin Mania

    在2025年,与知名品牌支持者一起探索最佳的加密货币。深入了解模因币热潮,并获得关于拖钓猫、庞克等项目的洞察。 Meme Coin 热潮席卷2025!诸如Bonk、Lofi及热门预售项目正引发关注。让我们深入挖掘那些价格亲民且有名人背书的加密资产。 拖钓猫:值得关注的模因币 Troller Cat(…

    2025年12月8日
    000
  • 以太坊,波纹和美联储降低:纽约客对加密货币的下一章的看法

    探索以太坊、波纹与潜在降息之间的联动效应。把握加密货币市场的投资机遇与趋势走向。 嘿,加密世界又热闹起来了。以太坊、波纹以及关于降息的猜测正搅动市场风云,仿佛高峰时段的地铁站般喧嚣不断。这是数字资产领域的焦点所在。 以太坊:去中心化金融的核心支柱 以太坊(Ethereum)依然是去中心化金融(DeF…

    2025年12月8日
    000
  • 欧易OKX和币安怎么选?2025加密货币交易所对比指南

    在飞速发展的加密货币市场中,选择一个合适的交易平台是每个参与者面临的关键决策。进入2025年,随着市场的成熟和监管环境的变化,主要交易所之间的差异化竞争愈发明显。欧易okx和币安作为全球领先的加密货币交易所,各有其特色和优势。理解它们的服务内容、技术特点和市场定位,对于投资者和交易者至关重要。 欧易…

    2025年12月8日
    000
  • 币安vs火币htx 手续费、安全、用户体验全方位深度对比

    币安和火币HTX是全球领先的加密货币交易所,各有优势。1. 币安成立于2017年,交易量大、流动性强,提供丰富的金融衍生品及全面的生态系统服务;2. 火币HTX成立于2013年,历史悠久,在亚洲市场特别是华人社群中用户基础深厚;3. 手续费方面,两者均采用阶梯式费率结构,普通用户通过持有平台币可享受…

    2025年12月8日
    000
  • 比特币矿工与黄金矿工有什么区别?

    比特币矿工与黄金矿工的核心区别在于:1.本质不同,比特币挖矿是维护区块链网络安全的分布式记账过程,而黄金挖矿是资源开采和提炼;2.资源类型不同,比特币为虚拟资产且总量固定,黄金为实物贵金属且储量未知;3.挖矿方式不同,比特币依赖高性能计算机和电力,黄金依赖重型机械、化学提炼和人力;4.成本和风险不同…

    2025年12月8日
    000
  • okx跟火币网相比有什么不同

    okx与火币网(现已更名为htx)作为全球知名的加密资产交易平台,为广大用户提供了丰富的数字资产交易与管理服务。它们在品牌历史、产品侧重、用户体验及生态布局上存在着显著的差异。理解这些不同之处,有助于用户根据自身的需求和偏好做出更合适的选择。 核心优势与市场定位对比 OKX的核心竞争力体现在其全面的…

    2025年12月8日
    000
  • pepe、doge、shibi的区别分析

    在数字货币的广阔世界里,doge、shib和pepe这三个名字常常被一同提及。它们都源于互联网迷因(meme)文化,凭借着强大的社区共识和病毒式传播,在市场中占据了一席之地。剖析它们各自的特点,可以发现它们在起源、技术架构、经济模型和社区生态上存在着显著的差异。理解这些差异,是认知此类数字资产的关键…

    2025年12月8日
    000
  • 2025年狗狗币走势的关键变量分析

    影响2025年狗狗币走势的关键变量组合,要理解2025年狗狗币的可能路径,我们需要拆解影响其价格的几个核心驱动变量。这些变量如同密码锁上的数字拨轮,不同的组合会解锁完全不同的结果。 变量A:全球宏观经济的“风向” 加密货币市场作为一个新兴的风险资产类别,其资金流动与全球宏观经济环境紧密相连。当全球主…

    2025年12月8日
    000
  • 稳定币跟比特币有什么关联与区别

    稳定币跟比特币有什么关联与区别 稳定币与比特币同属于数字货币的范畴,都在区块链技术的基础上运行,但它们的设计初衷、价值来源和市场功用存在着本质的不同。简单来说,比特币是一种追求价值增长与去中心化自由的数字资产,而稳定币的核心目标是维持价值的稳定,充当数字世界中的“锚定货币”。 两者之间的深层联系 尽…

    2025年12月8日
    000
  • 拖钓猫:模因硬币在2025年扑灭了吗?

    忘记道路。 troller cat($ tcat)通过堆积,游戏燃烧和真正的实用程序将其拖到顶部。这是2025年的模因硬币吗? 模因硬币市场正处于狂热状态,但可以确定的是:它不会消失。别再执着于熟悉的生活;在2025年,模因硬币的目标是实现实际价值。像喜剧演员、测试项目以及一只调皮的猫科动物这样的名…

    2025年12月8日
    000

发表回复

登录后才能评论
关注微信