在使用数据库时,为什么要使用Java 8 Stream API而不是直接的hibernate/sql查询

str*_*ash 20 java hibernate java-8 java-stream

最近我在很少的项目中看到很多代码使用流来过滤对象,比如:

library.stream()
          .map(book -> book.getAuthor())
          .filter(author -> author.getAge() >= 50)
          .map(Author::getSurname)
          .map(String::toUpperCase)
          .distinct()
          .limit(15)
          .collect(toList()));
Run Code Online (Sandbox Code Playgroud)

使用它而不是直接HQL/SQL查询到数据库返回已经过滤的结果是否有任何好处.

第二种方法不是更快吗?

Hen*_*nry 31

如果数据最初来自数据库,最好在数据库中进行过滤,而不是获取所有内容并在本地过滤.

首先,数据库管理系统擅长过滤,它是主要工作的一部分,因此针对它进行了优化.也可以使用索引加速过滤.

其次,获取和传输许多记录并将数据解组成对象只是为了在进行本地过滤时丢掉大量数据而浪费带宽和计算资源.

  • @Eugene,因为`fetchAll,filter later`操作意味着浪费了被`filter`操作丢弃的每个实体的系统资源,因为我们实际上强迫数据库和我们的映射框架做了比我们需要更多的工作.这应该在答案中解释. (10认同)
  • @Eugene,完全同意你的意见,这个答案应该扩大一些解释. (3认同)

Gho*_*ica 25

乍一看:溪流可以平行运行; 只需更改代码即可使用parallelStream().(免责声明:当然,如果只改变流类型将导致正确的结果,它取决于具体的上下文;但是,它可以很容易).

然后:流"邀请"使用lambda表达式.而这些又导致使用invoke_dynamic字节码指令; 与"老派"编写此类代码相比,有时会获得性能优势.(并澄清误解:invoke_dynamic是lambda的属性,而不是stream!)

这些将是现在更喜欢"流"解决方案的理由(从一般观点来看).

除此之外:它真的取决于...让我们来看看您的示例输入.这看起来像处理已经驻留在内存中的普通Java POJO,在某种集合中.直接在内存中处理这些对象肯定比去一些进程外数据库在那里工作更快!

但是,当然:当上述呼叫时,就像book.getAuthor()进行"深度探索"并实际与底层数据库交谈一样; 然后很有可能"在一个查询中完成整个事情"会给你带来更好的表现.

  • 一个有趣的可能性是上面的调用链实际上是一个流畅的构建器,它懒惰地构造一个查询,Iow可能是它实际上是#1*和*#2:使用流API,但是在数据库中做所有事情.如果不知道这些方法的实现,你无法从问题的片段中分辨出来. (10认同)
  • @JörgWMittag,也就是说,查询构建器可以在例如.NET中使用Linq,但不能在Java中工作:普通java中没有办法告诉lambda做什么,特别是`author.getAge()的部分> 50`,其中没有查询构建器可以检查并获得对`> 50`部分的访问 - 这只能在编译时使用,基本上,查询构建器可以看到未解释的Java文本.如果该行看起来像`author.getAge().greaterThan(50)`或其他东西,则可以进行可解释的查询. (6认同)
  • @JörgWMittag,我不会说它们本身就被破坏了 - Java只是不允许你覆盖它们,这也可以被看作是一件好事(因为它让你放心猜测那个运算符会是什么在执行时以及是否在任何时候被覆盖,它是否会产生副作用以及它对任何给定输入的影响. (3认同)
  • @JörgWMittag,嗯,运算符不是真正的方法,因为它们有一个优先级的概念,哪些方法没有 - 至少在Java中.有些语言取消了运算符优先级,以强化"运算符是一个有趣名称的方法",而这种移动在我眼中(以及许多其他人)违反了最不惊讶的原则.我们不太可能就此达成一致意见,所以是的,这将是另一个时间讨论的问题. (3认同)
  • @JörgWMittag但请记住,某些库*做*做奇怪的事情涉及解析字节码.这不是不可能的,甚至是难以置信的. (3认同)

Jen*_*der 13

首先要意识到,您无法从这段代码中分辨出针对数据库发出的语句.很可能,收集了所有过滤,限制和映射,并且在调用collect所有信息后,用于构造匹配的SQL语句(或使用的任何查询语言)并发送到数据库.

考虑到这一点,使用流式API的原因有很多.

  1. 这是时髦的.Streams和lambdas对于大多数Java开发人员来说仍然是一个新手,所以当他们使用它时他们感觉很酷.

  2. 如果使用第一段中的内容,它实际上创建了一个很好的DSL来构造您的查询语句.Scalas Slick.Net LINQ我知道的早期例子,虽然我假设有人在我出生之前很久就在LISP中构建类似的东西.

  3. 流可能是反应流并封装非阻塞API.虽然这些API非常好,因为它们不会强迫您在等待结果时阻止线程等资源.使用它们需要大量的回调或使用更好的基于流的API来处理结果.

  4. 他们更好地阅读命令式代码.也许在流中完成的处理不能[轻松/由作者]完成SQL.因此,替代方案不是SQL与Java(或您正在使用的语言),而是命令式Java或"功能"Java.后者经常读得更好.

所以有充分的理由使用这样的API.

尽管如此:在几乎所有情况下,当您可以将其卸载到数据库时,在应用程序中进行任何排序/过滤等都是一个坏主意.我目前唯一能想到的例外是你可以跳过整个往返数据库,因为你已经在本地获得了结果(例如在缓存中).


Eug*_*ene 5

除非针对特定情况进行测量和验证,否则可能是好的或同样糟糕的.您通常希望对数据库进行这类查询的原因是(除其他外):

DB可以处理比java进程更大的数据

可以索引数据库中的查询(使它们更快)

另一方面,如果您的数据很小,那么使用Stream您的方式是有效的.编写这样的Stream管道非常易读(一旦你 Streams足够好).


The*_*ind 5

那么,理想情况下你的问题应该是 - 在数据库中进行缩减/过滤操作或获取所有记录并使用Streams在Java中执行它是否更好?

答案并不简单,任何给出"具体"答案的统计数据都不会推广到所有情况.

您正在谈论的操作最好在DB本身中完成,因为这是DB的设计目标,非常快速地处理数据.当然,通常在关系数据库的情况下,会有一些"簿记和锁定"用于确保独立事务不会最终导致数据不一致,但即使如此,DB在过滤方面也做得非常好数据,尤其是大型数据集.

如果您需要从相同数据中过滤不同的功能,我倾向于使用Java代码而不是数据库过滤数据.例如,现在您只获得作者的姓氏.如果你想获得作者所写的所有书籍,作者的年龄,作者的孩子,出生地等等.那么从数据库中只获得一个"只读"副本并使用并行流来获取不同的信息是有意义的来自相同的数据集.