如果你是一位Java开发者,渴望拥有一种更简洁直观的查询数据库的方式,类似于C#中优雅的LINQ语法,那么你应该尝试一下开源库JPAStreamer。在本文中,我将探索这个强大的Java工具的功能,它为你的Hibernate查询带来了类似LINQ的体验。
在深入研究JPAStreamer之前,我将介绍LINQ的一些理想特性。LINQ(Language Integrated Query)是C#中的一种表达式,可以从各种数据源中转换数据,从简单的线性数据结构到数据库。下面是LINQ文档中的一个基本示例,它操作一个整数数组,查找大于80的任意分数:
int[] scores = { 97, 92, 81, 60 };
IEnumerable scoreQuery =
from score in scores
where score > 80
select score;
foreach (int i in scoreQuery)
{
Console.Write(i + " "); // Output: 97 92 81
}
如上所示,LINQ API与SQL语法非常相似,但在C#环境中使用时是类型安全的。在Java中,我们可以使用Java 8引入的流式API以一种流畅而直观的方式表达相同的操作:
final int[] scores = {97, 92, 81, 60}
Stream.of(scores)
.filter(score -> score > 80)
.foreach(System::out);
虽然Java流式API与SQL不完全相同,但其中许多操作与SQL具有相似性,例如filter(where)、sorted(order)和map(select)。
无论是LINQ还是流式API,都强调声明式编程风格,允许开发人员表达他们想要实现的目标,而不是具体指定如何实现它。这种方法增强了代码的可读性和可维护性。
不,情况并不那么简单。假设我们的分数代表来自游戏应用的历史结果,因此它们存储在数据库中而不是内存数组中。为了激励玩家,我们想要展示一个有关历史最高分的列表。通过使用LINQ to SQL,输入数组可以简单地替换为对对象模型实体的引用,即数据库的C#元模型。在执行时,LINQ将自动向数据库发出本地查询,并配备了连接操作和查询推导语法以编写更复杂的查询。
然而,Java流式API不能直接转换为SQL查询…或者说它们确实可以吗?
JPAStreamer是一个开源的JPA扩展,它在Java流式API和数据库查询之间建立了桥梁。通过使用Java流式API的自定义实现,JPAStreamer可以自动将流式操作转换为高效的数据库查询,并支持连接和投影操作以覆盖更多的场景。
与LINQ to SQL不同,JPAStreamer并不是一个独立的ORM,而是使用现有的JPA提供者(如Hibernate)来管理由标准JPA实体组成的数据库元模型。接下来,假设游戏应用程序使用Hibernate来管理历史游戏会话。过去的分数在数据库中表示为一个Score表,我们的JPA实体模型中有一个关联的Score实体,如下所示:
@Table(name = "score", schema = "game-db")
public class Score implements Serializable {
public Score() {}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "score_id", nullable = false, updatable = false, columnDefinition = "smallint(5)")
private Integer scoreId;
@Column(name = "value", nullable = false, columnDefinition = "varchar(255)")
private Integer value;
@Column(name = "date", nullable = false, columnDefinition = "timestamp")
private LocalDateTime date;
@ManyToOne
@JoinColumn(name="username", nullable = false, updatable = false, insertable = false)
private Player player;
// Setters and getters are left out for clarity…
}
如上所示,分数可以由ID、值、游戏日期和相关的玩家来描述。一旦JPA元模型就位,就可以通过添加以下Maven依赖项来安装JPAStreamer:
com.speedment.jpastreamer
jpastreamer-core
3.0.1
(有关Gradle安装,请在此处阅读更多信息。如果您正在使用Quarkus生态系统,Quarkiverse中提供了一个JPAStreamer扩展。)
安装后,需要重新构建项目以触发JPAStreamer自己的元模型生成过程。该过程完全自动化,输出可以在target/generated-sources文件夹中找到。在这种情况下,JPAStreamer会生成一个Score 和 P l a y e r 和Player 和Player类,我们可以使用它们来构建可以由JPAStreamer查询优化器解释的谓词。
听起来有点模糊?让我们通过编写一个过滤出前10个分数的查询来具体说明:
JPAStreamer jpaStreamer = JPAStreamer.of(“game-db”);
jpaStreamer.stream(Score.class)
.sorted(Score$.value.reversed())
.limit(10)
.foreach(System::out);
JPAStreamer可以通过一行代码实例化,通过引用持久化单元的想象名称“game-db”。第二行创建了一个从JPA实体Score中获取的流。然后对分数进行从高到低的排序,并选择前10个结果。与LINQ类似,查询的执行被延迟到调用终端操作时;在本例中,是用于打印分数的foreach操作符。最重要的是,这个流不会将数据库中的所有分数实例化,因为它会自动转换为一个SQL查询:
select
score0_.score_id as score_id1_0_,
score0_.value as value2_0_,
score0_.date as date3_0_,
score0_.username as username4_0_,
from
score score0_
order by
score0_.value dsc limit ?, ?
为了给高分榜上的玩家以荣誉,我们可能还想显示与每个分数相关联的玩家的姓名。如上所示的Score实体可以看出,它与Player类有一个多对一的关系。我不会展示一个想象中的JPA Player实体,但是每个玩家都通过其用户名进行唯一标识,并具有名字和姓氏字段。
我们可以通过连接每个分数的玩家来获取关联玩家的姓名。使用JPAStreamer意味着我们需要更新流的源,不仅包括Score实体,还包括关联的Player对象。这个连接是使用Stream Configuration来定义的,如下所示。与之前一样,我们收集前十个分数,并将这些条目映射到它们对应的玩家。最后,玩家和他们的分数将被打印在控制台上。
JPAStreamer jpaStreamer = JPAStreamer.of("game-db");
Map scoreMap = jpaStreamer.stream(
StreamConfiguration.of(Score.class).joining(Score$.player))
.sorted(Score$.value.reversed())
.limit(10)
.collect(toMap(
Function.identity(),
Score::getPlayer
)
);
scoreMap.forEach(
(s, p) -> System.out.format("%s %s: %s points\n",
p.getFirstName(),
p.getLastName(),
s.getValue()
)
);
JPAStreamer将类型安全和流畅的查询能力引入到Java开发领域,为开发人员提供了一种直观且表达力强的方式来与关系数据库进行交互。通过其声明性语法、延迟评估和可组合性,JPAStreamer与C#的LINQ库有着明显的相似之处。通过采用JPAStreamer,Java开发人员可以从一种简洁高效的方式来查询数据库中受益,从而最终实现更清晰和更易于维护的代码。
更多技术干货请关注公号“云原生数据库”
squids.cn,基于公有云基础资源,提供云上 RDS,云备份,云迁移,SQL 窗口门户企业功能,
帮助企业快速构建云上数据库融合生态。