【译】Spring I&O 社区专家聊 Jimmer ORM
近日,Spring I&O 社区专家 Polivakha Mikhail 发布了一篇关于 Jimmer ORM 框架的文章,由 Baeldung 发表。
这是专门为 SpringIO 社区准备的修订和扩展版本。在其中,Mikhail 揭示了 Jimmer 的主要特征: 没有 JPA 传统、声明式 DTO 和灵活的 DSL,以及与 Spring 的潜在集成。
原文地址:Baeldung - Introduction to Jimmer ORM
下文为原文翻译,如有错译漏译,欢迎指正!Jimmer 也是我非常喜欢的 ORM 框架,希望能借由 Mikhail 之笔,带你领会 Jimmer 的魅力~
1. 简介
在本教程中,我们将回顾 Jimmer ORM 框架。 这个 ORM 在本文撰写时相对较新,但它具有一些很有前途的特性。 我们将回顾 Jimmer 的理念,然后用它编写一些示例。
2. 整体架构
首先,Jimmer 不是 JPA 的实现。这意味着 Jimmer 并不实现所有 JPA 功能。 例如,Jimmer 没有脏检查机制。 然而,值得一提的是,Jimmer 与 Hibernate 一样,有许多相似的概念。 这是有意为之,目的是使从 Hibernate 过渡更加顺畅。 因此,总的来说,JPA 知识对于理解 Jimmer 会很有帮助。
举个例子,Jimmer 有实体的概念,尽管其形状和设计与 Hibernate 有很大不同。 然而,像懒加载或级联这样的概念在 Jimmer 中并不存在。 原因是由于 Jimmer 的设计方式,这些概念在 Jimmer 中并没有太大意义。我们很快就会看到这一点。
本节的最后一点:Jimmer 支持多种数据库, 包括 MySQL、Oracle、PostgreSQL、SQL Server、SQLite 和 H2。
3. 实体示例
如前所述,Jimmer 与 Hibernate 和许多其他 ORM 框架有很多不同之处; 它有几个关键的设计原则。 第一个是我们的实体只有一个目的 - 表示底 层数据库的模式。 但是,这里重要的一点是,我们不通过注解指定与之交互的方式。 相反,Jimmer 要求开发者提供所有必要的信息,以便在调用点派生要执行的查询。
那么,这意味着什么?为了理解,让我们回顾以下 Jimmer 实体:
import org.babyfish.jimmer.client.TNullable;
import org.babyfish.jimmer.sql.Column;
import org.babyfish.jimmer.sql.Entity;
import org.babyfish.jimmer.sql.GeneratedValue;
import org.babyfish.jimmer.sql.GenerationType;
import org.babyfish.jimmer.sql.Id;
import org.babyfish.jimmer.sql.JoinColumn;
import org.babyfish.jimmer.sql.ManyToOne;
import org.babyfish.jimmer.sql.OneToMany;
@Entity
public interface Book {
@Id
@GeneratedValue(strategy = GenerationType.USER)
long id();
@Column(name = "title")
String title();
@Column(name = "created_at")
Instant createdAt();
@ManyToOne
@JoinColumn(name = "author_id")
Author author();
@TNullable
@Column(name = "rating")
Long rating();
@OneToMany(mappedBy = "book")
List<Page> pages();
}
如你所见,它有类似于 JPA 的注解。
但缺少一件事 - 我们没有为关系指定任何级联,比如我们例子中的 pages。
类似地,对于获取类型(懒加载或急加载) -
在声明端 - 没有指定。
我们也不能指定 @Column 注解的 insertable 或 updatable 属性,就像我们在 JPA 中可能会做的那样,等等。
我们不这样做是因为 Jimmer 期望我们在尝试执行相应操作时明确提供它。 我们将在下面的章节中详细了解这一点。
4. DTO 语言
另一件立即引起我们注意的事情是 Book 是一个接口,而不是一个类。 这是有意为之的,因为在 Jimmer 中,我们不应该直接使用实体, 也就是说,我们不应该实例化它们。相反, 假设我们将通过 DTO 读取和写入数据。 这些 DTO 应该具有我们想要从数据库写入或读取的确切形状。 让我们看一个例子(现在不要关注我们所做的确切 API 调用):
public void saveAdHocBookDraft(String title) {
Book book = BookDraft.$.produce(bookDraft -> {
bookDraft.setCreatedAt(Instant.now());
bookDraft.setTitle(title);
bookDraft.setAuthor(AuthorDraft.$.produce(authorDraft -> {
authorDraft.setId(1L);
}));
bookDraft.setId(1L);
});
sqlClient.save(book);
}
一般来说,在大多数交互中,我们需要使用 SqlClient 与数据库交互。
在上面的示例中,我们通过 BookDraft 接口创建了一个临时 DTO。 Jimmer 为我们生成了 BookDraft 接口以及 AuthorDraft,这不是手写代码。 如果我们使用 Java,生成本身是在编译时通过 Java 注解处理工具(APT)完成的, 或者如果我们使用 Kotlin,则通过 Kotlin Symbol Processing(KSP)。
这两个生成的接口允许构建任意形状的 DTO 对象, Jimmer 内部稍后将其转换为 Book 实体。 所以,我们确实在保存一个实体,只是我们不是自己实例化它,而是 Jimmer 为我们做的。