如何使用Java Streams进行数据库查询?

本文介绍了如何编写能够处理现有数据库数据的Java应用程序,而无需编写单行SQL(或类似语言)代码,也无需浪费时间将所有的内容组合在一起。在您的应用程序准备就绪之后,您可以通过添加两行代码,使用in-JVM-acceleration加速性能。

本文,我们使用Speedment框架,可以直接从数据库模式生成代码,并可以自动将Java Streams生成SQL,您可以使用Java编写代码。

示例数据库

我们使用的示例数据库是Sakila。它有Film(影片), Actor(演员), Category(类别)等表格,下载地址: https://dev.mysql.com/doc/index-other.html

步骤一:连接数据库

我们使用 Speedment Initializer 配置pom.xml文件,点击下载后,将得到一个带有自动生成Main.java文件的项目文件夹。

然后,解压文件夹zip.,打开命令行,然后转到pO.xml文件所在的位置。输入以下命令:


mvn speedment:tool

Speedment将被启动,会提示您输入授权码。选择“Start Free”,您将获得免费许可。然后便可以连接数据库开始使用。

步骤二:生成代码

当数据库开始加载schema data时,便可以点击"Generate"生成完整的Java域模型。

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

步骤二中还会自动生成一个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 stream将自动生成SQL。为了查看SQL代码,需要修改Application Builder,并使用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:[]

SQL代码会因您选择的数据库类型而异(例如MySQL,MariaDB,PostgreSQL,Oracle,MS SQL Server,DB2,AS400等),且这些变化都是自动的。

上面的代码将产生以下输出(简洁为主)


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

步骤四:使用过滤器

Speedment流包括过滤器在内的所有流操作。假设我们只想过滤掉那些超过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。

步骤五:定义元素的顺序

默认情况下,出现在流中的元素是未被定义的。想要定义一个特定顺序,您需要将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(). I.e.字段方法。例如:sorted(Film.LENGTH.thenComparing(Film.TITLE.comparator()))。

步骤六:避免大对象块(Large Object Chunks)

人们一般会对结果进行分页来避免使用不必要的大对象块(Large Object Chunks)。假设我们希望在每页看到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代码就会不同。

步骤七: In-JVM-Memory加速

由于您在初始化程序中使用了标准配置,所以In-JVM-memory加速在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();

现在,表流将直接在RAM中被提供,而不是生成SQL查询。内存索引也将加速过滤、排序和跳过。内存表和索引都储存在堆外,避免了垃圾回收的延迟。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31542119/viewspace-2214198/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31542119/viewspace-2214198/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值