在本文中,您将学习如何编写纯Java应用程序,这些应用程序能够处理现有数据库中的数据,而无需编写一行SQL(或类似的语言,如HQL),而无需花费数小时将所有内容放在一起。 准备好应用程序之后,您将通过仅添加两行代码来学习如何使用JVM内加速将延迟性能提高1000倍以上。
在本文中,我们将使用Speedment,它是一个Java流ORM,可以直接从数据库模式生成代码,并且可以自动将Java Streams直接呈现为SQL,从而允许您用纯Java编写代码。
您还将发现,通过直接在RAM中运行流的JVM内存技术可以显着提高数据访问性能。
示例数据库
我们将使用来自MySQL的示例数据库Sakila。 它具有称为电影,演员,类别等的表格,可以在此处免费下载。
步骤1:连接到数据库
我们将开始使用可以在此处找到的Speedment Initializer配置pom.xml
文件。 按“下载”,您将获得带有自动生成的Main.java
文件的项目文件夹。
接下来,解压缩项目文件夹的zip文件,打开命令行,转到解压缩的文件夹(pom.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,AS400等),呈现的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:使用筛选器
Speedment流支持所有Stream操作,包括过滤器。 假设我们只想过滤那些长于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分钟或长于1小时的电影。 检查您的日志文件,您还将看到此Stream也呈现为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);
这将按LENGTH顺序(升序)然后按TITLE顺序(降序)对影片元素进行排序。 您可以组成任意数量的字段。
注意:如果要按升序组成两个或多个字段,则应使用该字段的方法
.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);
}
此实用程序方法可以使用ANY过滤器来分页ANY表,并按ANY顺序对其进行排序。
例如,调用:
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
文件中启用了In-JVM内存加速。 要在应用程序中激活加速,只需修改初始化代码,如下所示:
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();
现在,表流将直接从RAM提供,而不是呈现SQL查询。 内存索引也将加快筛选,排序和跳过。 内存中表和索引都存储在堆外,因此它们不会增加垃圾回收的复杂性。
在我的笔记本电脑上(Mac Book Pro,15英寸,2015年中,16 GB,i7 2.2 GHz),对于我计算与过滤器匹配的电影和经过分类的电影流相比,与之竞争的电影流,查询延迟减少了1000倍以上。在本地计算机上运行的MySQL数据库(版本5.7.16)的标准安装。
摘要
在本文中,您了解了使用纯Java流查询现有数据库有多么容易。 您还了解了如何使用JVM内存流技术加快对数据的访问。 Sakila数据库和Speedment均可免费下载和使用,请自己尝试。
翻译自: https://www.javacodegeeks.com/2018/09/query-databases-using-java-streams.html