您几乎可以看到数据在流动...
SQL一直是一种声明性语言,而Java长期以来势在必行。Java流改变了游戏规则。通过本动手文章编写您的方式,并学习如何使用Java流对RDBMS数据库执行声明性查询,而无需编写任何SQL代码。您会发现,Java流和SQL命令的动词之间有着惊人的相似性。
不是您要找的东西?看看 使用Java Streams查询数据库。
本文是五分之四,另外还有一个GitHub存储库,其中包含每个单元的说明和练习。
数据库流
当您熟悉Streams的操作时,您可能已经注意到与SQL构造的相似之处。它们中的一些或多或少具有与Stream操作的直接映射,例如LIMIT
和COUNT
。开源项目Speedment利用这种相似性,使用纯Java提供对任何关系数据库的类型安全访问。
下表显示了Speedment如何在SQL流与Java流之间进行映射。
我们是Speedment开源项目的贡献者,我们将描述Speedment如何允许我们使用数据库作为流源,并使用来自任何数据库表的行向管道馈送数据。
如上图所示,Speedment将建立与数据库的连接,然后可以将数据传递给应用程序。不需要为数据库条目编写任何代码,因为Speedment会分析基础数据库并自动生成域模型所需的所有实体类。当您不必为要使用的每个表手动编写和维护实体类时,它可以节省大量时间。
Sakila数据库
为了本文和练习的方便,我们使用MySQL示例数据库Sakila作为我们的数据源。Sakila数据库为老式电影租赁业务建模,因此包含诸如Film和Actor之类的表。数据库实例已部署在云中,并且可以公开访问。
速度管理器
在Speedment中,数据库表的句柄称为Manager
。管理器是自动生成的代码的一部分。
A Manager
充当数据库表的句柄,并且可以充当流源。在这种情况下,每一行都对应一个Film实例。
一个Manager
在Speedment是通过调用实例:
FilmManager 电影 = 速度。getOrThrow(FilmManager。类);
注意:Speedment是可以从ApplicationBuilder获得的实例(在下一篇文章中对此主题有更多的了解)。
如果FilmManager::stream
调用,则结果是a Stream
,我们可以自由地对其应用任何中间或终端操作。首先,我们将收集列表中的所有行。
列出< 电影> allFilms = 电影。流()。收集(toList());
FilmImpl { filmId = 1,标题 = ACADEMY DINOSAUR,…
FilmImpl { filmId = 2,标题 = ACE GOLDFINGER,…
FilmImpl { filmId = 3,标题 = 适应 HOLES,...
…
过滤和计数
让我们看一个简单的示例,该示例输出评级为“ PG-13”的电影数量。就像常规Stream
影片一样,我们可以过滤出具有正确评分的电影,然后对这些作品进行计数。
长 pg13FilmCount = 电影。流()
。滤镜(电影。额定。等于(“ PG-13”))
。数();
pg13FilmCount:195
Speedment自定义实现Streams后的一个重要属性是,流能够通过自省来优化自己的管道。看起来Stream会遍历表的所有行,但事实并非如此。相反,Speedment能够将管道转换为传递给数据库的优化SQL查询。这意味着仅将相关的数据库条目提取到流中。因此,在上面的示例中,该流将自动呈现为类似于“ SELECT ... FROM film WHERE rating ='PG-13'”的SQL
这种自省要求将匿名lambda的任何使用(不包含与目标列相关的任何元数据)替换为Speedment Fields中的谓词。在这种情况下,当且仅当该电影的评分为PG-13时,才返回将在每部电影上进行测试的Film.RATING.equal("PG-13")
a Predicate
并返回true。
虽然,这并不妨碍我们将谓词表示为:
。过滤器(˚F - > ˚F。getRating。()等于(“PG-13” ))
但这将迫使Speedment提取表中的所有行,然后应用谓词,因此不建议这样做。
寻找最长的电影
这是一个示例,该示例使用带有-的max-operator在数据库中找到最长的电影Field Film.LENGTH
:
可选的< Film > longestFilm = 电影。流()
。最大值(薄膜。LENGTH);
最长电影:
可选 [ FilmImpl { filmId = 141,标题 = CHICAGO NORTH,长度 = 185,...}]
寻找三部短片
找到三部短片(我们定义为短于<= 50分钟)可以通过过滤掉50分钟或更短的任何片并挑选出前三个结果来完成。该示例中的谓词查看“长度”列的值,并确定该值是否小于或等于50。
列出< 电影> threeShortFilms = 电影。流()
。过滤器(电影。LENGTH。lessOrEqual(50))
。极限(3)
。收集(toList());
threeShortFilms:[
FilmImpl { filmId = 2,长度 = 48,..},
FilmImpl { filmId = 3,长度 = 50,… },
FilmImpl { filmId = 15,长度 = 46,...}]
分页分页
如果要在网站或应用程序中显示所有电影,我们可能希望对项目进行分页,而不是一次(可能)加载数千个条目。这可以通过结合操作skip()和来完成limit()
。在下面的示例中,假设每个“页面”包含25个条目,我们将收集第二页的内容。回想一下,Streams不能保证元素的特定顺序,这意味着我们需要使用sorted-operator定义一个顺序才能使其按预期工作。
列出< 电影> filmsSortedByLengthPage2 = 电影。流()
。分类(电影。长度)
。跳过(25 * 1)
。极限(25)
。收集(toList());
filmsSortedByLengthPage2:
[ FilmImpl { filmId = 430,长度 = 49,… },… ]
注意:查找第n页的内容是通过跳过(25 *(n-1))完成的。
注意2:此流将自动呈现为“ SELECT ... FROM film ORDER BY length ASC LIMIT?OFFSET?,值:[25,25]”
以“ A”开头的电影,按长度排序
我们可以轻松地找到任何以大写字母“ A”开头的电影,并根据其长度(以最短的电影为首)对它们进行排序,如下所示:
列出< 电影> filmsTitleStartsWithA = 电影。流()
。过滤器(电影。TITLE。startsWith(“A” ))
。分类(电影。长度)
。收集(收藏家。toList());
filmsTitleStartsWithA:[
FilmImpl { filmId = 15,标题= ALIEN CENTRE,…,等级= NC - 17,长度 = 46,
FilmImpl { filmId = 2,标题= ACE GOLDFINGER,…,等级= G,长度 = 48,
… ]
计算胶片长度的频率表
我们还可以利用groupingBy运算符根据其长度对存储桶中的胶片进行排序,并计算每个存储桶中的胶片总数。这将创建一个所谓的胶片长度频率表。
映射< Short,Long > frequencyTableOfLength = 电影。流()
。收集(收藏家。groupingBy(
电影。长度。asShort(),
数()
));
frequencyTableOfLength:{ 46 = 5,47 = 7,48 = 11,49 = 5,... }
练习题
对于本周的练习,您无需担心连接自己的数据库。相反,我们已经提供了到云中Sakila数据库实例的连接。像往常一样,练习可以位于该GitHub库。本文的含量足以解决这就是所谓的第四单元MyUnit4Database
。相应的Unit4Database
Interface包含JavaDocs,它们描述了中方法的预期实现MyUnit4Database
。
公共 接口 Unit4Database {
/ **
*返回数据库中的影片总数。
*
* @param电影实体的电影经理
* @返回数据库中的电影总数
* /
long countAllFilms(FilmManager 电影);
提供的测试(例如Unit4MyDatabaseTests
)将用作自动评分工具,让您知道您的解决方案是否正确。