首先,Jimmer不是一个JPA实现。这意味着Jimmer并没有实现所有JPA特性。例如,Jimmer本身没有脏检查机制。然而,值得一提的是,Jimmer和Hibernate一样,有很多类似的概念。这是为了使从Hibernate的过渡更平滑。因此,一般来说,JPA知识将有助于理解Jimmer。
例如,Jimmer有一个实体的概念,尽管它的形状和设计与Hibernate有很大的不同。然而,像延迟加载或级联这样的概念在Jimmer中并不存在。原因是它们在Jimmer中没有多大意义,因为它的设计方式。我们很快就会看到。
本节的最后说明:Jimmer支持多个数据库,包括MySQL、Oracle、PostgreSQL、SQL Server、SQLite和H2。
如前所述,Jimmer与Hibernate和许多其他ORM框架有很多不同之处;它有几个关键的设计原则。第一个是我们的实体服务于唯一的目的-表示底层数据库的模式。但是,这里重要的是,我们没有指定我们打算通过注释与它交互的方式。相反,Jimmer要求开发人员提供派生要在调用站点上执行的查询所需的所有信息。
那是什么意思为了理解,让我们回顾以下Jimmer实体:
import org.babyfish.jimmer.client.TNullable; |
正如您所注意到的,它具有类似于JPA的注释。但有一点是缺失的--我们没有为关系指定任何级联,例如本例中的页面。类似的获取类型(lazy或eager)-在声明端-没有指定。我们也不能像在JPA等中那样指定@Column注释的可插入或可更新属性。
我们不这样做,因为Jimmer希望我们在尝试执行适当的操作时显式地提供它。我们将在下面的章节中详细了解这一点。
DTO语言
另一件让我们立即想到的事情是,Book是一个接口,而不是一个类。这是故意的,因为在Jimmer中,我们不应该直接使用实体,也就是说,我们不应该实例化它们。相反,假设我们将通过DTO读写数据。这些DTO应该具有我们希望从数据库写入或读取的确切形状。让我们看一个例子(不要关注我们现在进行的确切的API调用):
public void saveAdHocBookDraft(String title) { |
一般来说,在大多数交互中,我们需要使用SqlClient来与数据库交互。
在上面的示例中,我们通过BookDraft接口创建了一个ad-hoc DTO。Jimmer为我们生成了与AuthorDraft一起沿着的BookDraft界面,它不是手写代码。如果我们使用Java,则在编译时通过Java注释处理工具生成,如果我们使用科特林,则通过科特林符号处理生成。
这两个生成的接口允许构造任意形状的DTO对象,Jimmer稍后在内部将其转换为Book实体。所以,我们确实在保存一个实体,只是我们不是自己实例化它,而是Jimmer为我们做的。
Null处理
此外,Jimmer将只保存DTO中存在的组件。这是因为Jimmer严格区分了最初未设置的属性和显式设置为null的属性。换句话说,如果我们不想在生成的SQL中包含给定的标量属性,我们只需创建一个DTO,而不显式设置它。标量是指不表示关系属性的字段:
public void insertOnlyIdAndAuthorId() { |
在上面的例子中,为Book生成的代码看起来像这样:
INSERT INTO BOOK(ID, author_id) VALUES(?, ?)
如果我们显式地将标量属性设置为null,那么Jimmer会将此属性包含在底层的UPDATE/UPDATE语句中,并为其分配一个null值:
public void insertExplicitlySetRatingToNull() { |
生成的SQL语句如下所示:
INSERT INTO BOOK(ID, author_id, rating) VALUES(?, ?, ?)
请注意,“等级”包括等级属性。在基础JDBC语句中,此rating属性的绑定值将被设置为null。
最后,对于表示关系的属性(非标量属性),其行为更加复杂,值得单独撰写一篇文章。
DTO爆炸问题
现在,有经验的开发人员可能会注意到一个问题。Jimmer处理数据库的方法意味着要创建几十个DTO,每个DTO都有一些独特的操作。答案是-不完全是。虽然我们确实需要很多DTO,但我们可以显著减少手动编写它们的开销。原因是Jimmer拥有专用的DTO语言。下面是一个例子:
export com.baeldung.jimmer.models.Book |
上面的例子代表了一个用Jimmer DTO语言编写的标记。与上一节中的示例一样,从这种标记语言生成POJO发生在编译期间。
例如,在上面的标记中,我们要求Jimmer使用#allScalars指令将所有标量字段包含在生成的DTO中。除此之外,我们还提到,DTO将只具有作者的ID,而不是作者本身。页面集合将完整地存在于DTO中(仅标量字段)。
因此,总的来说,对于Jimmer,我们确实需要大量DTO来描述每种情况下所需的行为。但是我们可以创建ad-hoc版本,或者依赖于编译器插件在构建过程中为我们生成的POJO。
读路径
到目前为止,我们只讨论了将数据保存到数据库中的方法。让我们回顾一下阅读路径。为了读取数据,我们还需要精确地指定需要通过DTO获取的数据。DTO的形状指示Jimmer需要获取哪些字段。如果DTO中不存在该字段,则不会获取该字段:
public List<BookView> findAllByTitleLike(String title) { |
在这里,我们使用上一节中的BookView DTO。我们还可以通过Fetcher的ad-hoc API指定需要读取的列。它与我们在写入数据库时使用的方法非常相似:
public List<BookView> findAllByTitleLikeProjection(String title) { |
在这里,我们使用Object Fetcher API来构造DTO,它表示我们想要读取的结构的形状。但是我们仍然在调用站点而不是声明站点上发送要读取的列的信号。此方法与临时创建DTO进行保存非常相似。
事务管理
最后,我们将快速回顾一下Jimmer管理事务的方式。一般来说,Jimmer本身没有内置的事务管理机制。因此,Jimmer严重依赖Spring Framework的事务管理基础设施。例如,让我们回顾一下本地事务管理的使用(非分布式),这是最常见的场景。在这种情况下,Jimmer依赖于Spring的TransactionSynchronizationManager功能和绑定到当前线程的transactional连接。
综上所述,Spring的@ transmix的传统用法将适用于Jimmer。通过Spring的TransactionTemplate进行命令式事务管理也可以用于Jimmer。
结论
在本文中,我们讨论了Jimmer ORM。正如我们所看到的,Jimmer在数据操作方面采取了独特的方法。特别是JPA和Hibernate,主要通过注释来表达与数据库的交互方式,而Jimmer要求开发人员在调用站点动态提供所有信息。为此,Jimmer使用DTO,我们通常会通过Jimmer本身使用其DTO语言生成DTO。但是,我们也可以创建它们。在事务管理方面,Jimmer依赖于Spring框架的基础设施。