使用Java流查询数据库

在本文中,您将了解如何编写纯Java应用程序,这些应用程序可以处理现有数据库中的数据,而无需编写一行SQL(或类似的语言,如HQL),并且无需花费大量时间将所有数据整合在一起。在应用程序准备好之后,您将学习如何通过只添加两行代码来提高延迟性能,使用JVM中的加速因子超过1,000。

在本文的整个过程中,我们将使用Speedment,它是一种Java流ORM,它可以直接从数据库模式生成代码,并且可以自动将Java流直接呈现给SQL,允许您用纯Java编写代码。

您还会发现,通过直接从RAM运行流的JVM内存技术,数据访问性能可以显著提高。

示例数据库

我们将使用MySQL中名为Sakila的示例数据库。它有电影、演员、类别等表格,可以免费下载。这里

步骤1:连接到数据库

我们将开始配置pom.xml通过使用您可以找到的Speedment初始化程序来创建这里。按“下载”,您将得到带有Main.java自动生成的文件。

接下来,解压缩项目文件夹zip文件,打开命令行,然后转到已解压缩的文件夹(open.xml文件所在的位置)。

然后,输入以下命令:


mvn speedment:tool


这将启动Speedment工具并提示您输入许可证密钥。选择“开始免费”,你将获得一个自动和免费的许可证。现在您可以连接到数据库并开始:

步骤2:生成代码

一旦从数据库加载了模式数据,就可以按下“Generate”按钮生成完整的Java域模型。

这只需要一两秒钟。

步骤3:编写应用程序代码

与步骤2中的域模型一起,自动生成了Speedment实例的生成器。打开Main.java文件中的代码并替换main()方法使用以下代码片段:


SakilaApplication app = new SakilaApplicationBuilder()
 
.withPassword("sakila-password") // Replace with your own password
 
.build();


接下来,我们将编写一个打印所有影片的应用程序。诚然,这是一个小应用程序,但我们将在本文的过程中对其进行改进。

// Obtains a FilmManager that allows us to
 
// work with the "film" table
 
FilmManager films = app.getOrThrow(FilmManager.class);
 
 
 
// Create a stream of all films and print
 
// each and every film
 
films.stream()
 
.forEach(System.out::println);


这不是很简单吗?

运行时,Java流将自动呈现到幕后的SQL中。为了实际查看呈现的SQL代码,请修改我们的应用程序生成器,并使用STREAM日志类型:


SakilaApplication app = new SakilaApplicationBuilder()
 
.withPassword("sakila-password")
 
.withLogging(ApplicationBuilder.LogType.STREAM)
 
.build();


运行应用程序时,SQL代码是这样的:


SELECT
 
`film_id`,`title`,`description`,`release_year`,
 
`language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
 
`length`,`replacement_cost`,`rating`,`special_features`,`last_update`
 
FROM
 
`sakila`.`film`,
 
values:[]


根据您选择的数据库类型(例如MySQL、MariaDB、PostgreSQL、Oracle、MS SQL Server、DB2、AS 400等),呈现的SQL代码可能有所不同。这些变化是自动的。

上面的代码将产生以下输出(为了简洁而缩短):


FilmImpl { filmId = 1, title = ACADEMY DINOSAUR, ..., length = 86, ... }
 
FilmImpl { filmId = 2, title = ACE GOLDFINGER, ..., length = 48, ...}
 
FilmImpl { filmId = 3, title = ADAPTATION HOLES, ..., length = 50, ...}
 
...


步骤4:使用过滤器

SpeedmentStreams支持所有的流操作,包括过滤器。假设我们只想过滤掉那些超过60分钟的电影。这可以通过将这一行代码添加到我们的应用程序中来实现:


films.stream()
 
.filter(Film.LENGTH.greaterThan(60))
 
.forEach(System.out::println);


呈现的SQL:

SELECT
 
`film_id`,`title`,`description`,`release_year`,
 
`language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
 
`length`,`replacement_cost`,`rating`,`special_features`,
 
`last_update`
 
FROM
 
`sakila`.`film`
 
WHERE
 
(`length` > ?),
 
values:[60]
 
产生的产出:
 
FilmImpl { filmId = 1, title = ACADEMY DINOSAUR, ..., length = 86, ... }
 
FilmImpl { filmId = 4, title = AFFAIR PREJUDICE, ..., length = 117, ...}
 
FilmImpl { filmId = 5, title = AFRICAN EGG, ... length = 130, ...}
 
可以组合过滤器来创建更复杂的表达式,如下所示:
 
films.stream()
 
.filter(
 
Film.LENGTH.greaterThan(60).or(Film.LENGTH.lessThan(30))
 
)
 
.forEach(System.out::println);


这将返回所有小于30分钟或超过一小时的电影。检查您的日志文件,您将看到这个流也被呈现到SQL中。

步骤5:定义元素的顺序

默认情况下,元素在流中的出现顺序未定义。若要定义特定顺序,请应用sorted()操作到这样的流:

films.stream()
 
.filter(Film.LENGTH.greaterThan(60))
 
.sorted(Film.TITLE)
 
.forEach(System.out::println);


呈现的SQL:

SELECT
 
`film_id`,`title`,`description`,`release_year`,
 
`language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
 
`length`,`replacement_cost`,`rating`,`special_features`,
 
`last_update`
 
FROM
 
`sakila`.`film`
 
WHERE
 
(`length` > ?)
 
ORDER BY
 
`length` ASC,
 
values:[60]
 
产生的产出:
 
FilmImpl { filmId = 77, title = BIRDS PERDITION,..., length = 61,...}
 
FilmImpl { filmId = 106, title = BULWORTH COMMANDMENTS,..., length = 61,}
 
FilmImpl { filmId = 114, title = CAMELOT VACATION,..., length = 61,..}
 
...
 
您还可以组合多个排序器来定义主顺序、次要顺序等。
 
films.stream()
 
.filter(Film.LENGTH.greaterThan(60))
 
.sorted(Film.LENGTH.thenComparing(Film.TITLE.reversed()))
 
.forEach(System.out::println);


这将按照长度顺序(升序)和标题顺序(降序)对电影元素进行排序。您可以组合任意数量的字段。

注:如果您正在按升序组成两个或多个字段,则应使用字段的方法.comparator()。即。sorted(Film.LENGTH.thenComparing(Film.TITLE.comparator()))而不仅仅是sorted(Film.LENGTH.thenComparing(Film.TITLE))

步骤6:页面和避免大对象块

通常,人们想要分页结果,以避免使用不必要的大对象块。假设我们希望看到每页50个元素,我们可以编写以下泛型方法:

private static final int PAGE_SIZE = 50;
 
 
 
public static <T> Stream<T> page(
 
Manager<T> manager,
 
Predicate<? super T> predicate,
 
Comparator<? super T> comparator,
 
int pageNo
 
) {

 
return manager.stream()
 
.filter(predicate)
 
.sorted(comparator)
 
.skip(pageNo * PAGE_SIZE)
 
.limit(PAGE_SIZE);
 
}


此实用程序方法可以使用任何筛选器对任何表进行分页,并按任何顺序对其进行排序。

例如,调用:

page(films, Film.LENGTH.greaterThan(60), Film.TITLE, 3)


将返回超过60分钟的电影流,并按显示第三页的标题排序(即跳过150部电影,放映以下50部电影)。

呈现的SQL:


SELECT
 
`film_id`,`title`,`description`,`release_year`,
 
`language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
 
`length`,`replacement_cost`,`rating`,`special_features`,
 
`last_update`
 
FROM
 
`sakila`.`film`
 
WHERE
 
(`length` > ?)
 
ORDER BY
 
`title` ASC
 
LIMIT ? OFFSET ?,
 
values:[60, 50, 150]


产生的产出:

FilmImpl { filmId = 165, title = COLDBLOODED DARLING, ... length = 70,...}
 
FilmImpl { filmId = 166, title = COLOR PHILADELPHIA, ..., length = 149... }
 
FilmImpl { filmId = 167, title = COMA HEAD, ... length = 109,...}
 
...

同样,如果我们使用了另一种数据库类型,则SQL代码将略有不同。

步骤7:JVM内存加速

由于您在初始化器中使用了标准配置,所以在您的pom.xml档案。要激活应用程序中的加速,只需修改初始化代码如下:

SakilaApplication app = new SakilaApplicationBuilder()
 
.withPassword("sakila-password")
 
.withBundle(InMemoryBundle.class)
 
.build();
 
 
 
// Load data from the database into an in-memory snapshot
 
app.getOrThrow(DataStoreComponent.class).load();


现在,不再呈现SQL查询,而是直接从RAM中提供表流.过滤、排序和跳过也将由内存中的索引加速.内存中的表和索引都是堆外存储的,因此它们不会增加垃圾收集的复杂性。

在我的笔记本电脑(MacBookPro,15英寸,中间2015,16 GB,i7 2.2GHz)上,与运行在本地计算机上的MySQL数据库(5.7.16版)标准安装相比,在我计算匹配过滤器的电影和排序流的流中,查询延迟减少了1,000多倍。

摘要

在本文中,您了解了使用纯Java流查询现有数据库是多么容易。您还了解了如何使用JVM内存流技术加速对数据的访问。Sakila数据库和Speedment都可以免费下载和使用,所以自己试试吧。