引见 Hibernate 数据存储库

引见 Hibernate 数据存储库

Jakarta Data 和 Jakarta Persistence 都使用实体类别以类型安全的方式来表示数据。由于 Hibernate 对 Jakarta Data 的实现以访问关系型数据库为后盾,因此这些实体类别是使用 Jakarta Persistence 定义的注释进行映射的。

例如

@Entity

public class Book {

@Id

String isbn;

@Basic(optional = false)

String title;

LocalDate publicationDate;

@Basic(optional = false)

String text;

@Enumerated(STRING)

@Basic(optional = false)

Type type = Type.Book;

@ManyToOne(optional = false, fetch = LAZY)

Publisher publisher;

@ManyToMany(mappedBy = Author_.BOOKS)

Set authors;

...

}

@Entity

public class Author {

@Id

String ssn;

@Basic(optional = false)

String name;

Address address;

@ManyToMany

Set books;

}

有关实体映射的更多信息,请参阅Hibernate 6 简介。

Jakarta Data 还可以使用 Jakarta NoSQL 定义的同名注释定义的实体。但在本指南中,我们使用的是 Hibernate Data Repositories,因此应将所有映射注释理解为在 jakarta.persistence 或 org.hibernate.annotations 中定义的注释。有关 Jakarta Data 中的实体的更多信息,请查阅该规范的第 3 章。

此外,查询可以用 HQL(即 Hibernate 的 Jakarta Persistence 查询语言 (JPQL) 超集)来表达。

Jakarta Data 规范定义了一个简单的 JPQL 子集,恰如其分地称为 JDQL。JDQL 主要与非关系型数据存储相关;预期由访问关系型数据的 Jakarta Data 实现支持 JPQL 的一个大得多子集。事实上,Hibernate Data Repositories 支持 JPQL 的超集。因此,即使我们在倡导、设计和指定 JDQL 方面付出了相当大的努力,但我们在这里不会详细介绍。有关 JDQL 的信息,请参阅 Jakarta Data 规范的第 5 章。

要了解有关 HQL 和 JPQL 的更多信息,请参阅 Hibernate 查询语言指南。

Jakarta Persistence 和 Jakarta Data 的相似之处到此结束。下表对比了这两种编程模型。

持久性

数据

持久性上下文

有状态

无状态

网关

EntityManager 接口

用户编写的 @Repository 接口

底层实现

会话

无状态会话

持久性操作

诸如 find()、persist()、merge()、remove() 等通用方法

带有 @Find、@Insert、@Update、@Save、@Delete 注解的类型安全用户编写方法

SQL 执行

在刷新期间

立即

更新

通常为隐式(在刷新期间进行脏检查)

始终显式(通过调用 @Update 方法)

操作级联

取决于 CascadeType

从不

延迟获取

隐式

使用 StatelessSession.fetch() 显式

JPQL 验证

运行时

编译时

此处的本质区别在于 Jakarta Data 不具有有状态持久性上下文。除其他影响外

实体实例总是分离的,因此

更新需要显式操作,并且

没有透明的延迟关联获取。

重要的是要了解 Hibernate Data Repositories 中的存储库由 StatelessSession 支持,而不是由 Jakarta Persistence EntityManager 支持。

在 Jakarta Data 中获取关联的唯一便携方式就是通过 JPQL join fetch 子句,在 @Query 注解 中。该规范没有提供获取关联的延迟便携方式。要获取关联,我们需要 直接调用 StatelessSession。这确实没有听起来那么糟糕;由于与数据库服务器来回进行过多通信,因此过度使用延迟获取与性能不佳相关。

Jakarta Data 的未来版本将具有由 Jakarta Persistence 有状态持久性上下文支持的存储库,但此功能未针对 Jakarta Data 1.0 进行裁剪。

第二个重大区别在于 Jakarta Data 要求与数据库交互时使用专用于单个实体类型的用户编写方法,而不是像能够对所有实体类执行持久化操作的 EntityManager 那样提供通用接口。该方法标记了允许 Hibernate 填充方法实现的注解。

例如,虽然 Jakarta Persistence 定义了 EntityManager 的 find() 和 persist() 方法,但在 Jakarta Data 中,应用程序员需要编写如下接口

@Repository

interface Library {

@Find

Book book(String isbn);

@Insert

void add(Book book);

}

这是我们第一个存储库示例。

1.1. 存储库接口

存储库接口是由您(应用程序员)编写的并使用 @Repository 注解的接口。存储库接口的实现由 Jakarta Data 提供商提供,在我们的示例中,由 Hibernate Data Repositories 提供。

Jakarta Data 规范并未说明应如何执行此操作,但在 Hibernate Data Repositories 中,该实现由一个注解处理器生成。事实上,您可能已经在使用此注解处理器:它只是现已不恰当地命名为 hibernate-jpamodelgen 模块中的 HibernateProcessor。

没错,我称之为 Hibernate Data Repositories 的花哨的东西实际上只是 Hibernate 值得尊重的静态元模型生成器的一种新功能。如果您已经在项目中使用 JPA 静态元模型,那么您就已经可以使用 Jakarta Data 了。如果您没有,我们将看到如何在 下一章 中进行设置。

当然,Jakarta Data 提供商无法生成任何任意方法的实现。因此,存储库接口的方法必须属于以下类别之一

default 方法,

使用 @Insert、@Update、@Delete 或 @Save 注解的 生命周期方法

使用 @Find 注解的 自动查询方法

使用 @Query 或 @SQL 注解的 带注解查询方法

资源访问器方法.

对于从 Spring Data 迁移过来的用户,Jakarta Data 还提供了一种名为按方法名查询的功能。对于新代码,我们不建议使用此方法,因为对于除最简单的示例之外的所有内容,它会产生冗长且不自然的名称。

我们很快会讨论每种方法。但首先我们需要提出一个更基本的问题:如何将持久化操作组织到存储库中,以及存储库接口与实体类型之间的关系是什么?也许令人惊讶的答案是:完全由您决定。

1.2. 组织持久性操作

Jakarta 数据允许你根据自己的喜好自由分配仓库中的持久性操作。特别是,Jakarta 数据不要求仓库界面集成声明基本“CRUD”操作的内置超类型,因此对于每个实体不必有单独的仓库界面。例如,你可以只使用单个 Library 界面,而不是 BookRepository、AuthorRepository 和 PublisherRepository。

因此,整个编程模型比旧有方法(例如 Spring Data,它要求每个实体类有一个仓库界面,或至少对于所谓的“聚合”有)要灵活得多。

“聚合”概念在诸如文档数据库中是有意义的。但是关系数据没有聚合,你应该避免试图用这种不恰当的方式将关系表硬塞进正在考虑的数据中。

为了使用户(特别是从旧有框架迁移过来的用户)更方便,Jakarta Data 确实定义了 BasicRepository 和 CrudRepository 界面,如果你喜欢的话可以使用它们。但在 Jakarta 数据中,这些界面不是特别重要的;使用相同的注释声明你自己的仓库的方法来声明其操作。以下示例说明了这种旧有、不灵活方法。

// old way

@Repository

interface BookRepository

extends CrudRepository {

// query methods

...

}

@Repository

interface AuthorRepository

extends CrudRepository {

// query methods

...

}

我们在这个文档中不会再看到 BasicRepository 和 CrudRepository,因为它们不是必需的,并且因为它们实现了较旧、不灵活的方式。

相反,我们的仓库通常会将处理若干个相关实体的操作分组在一起,即使实体没有单个“根”。这种情况在关系数据模型中很常见。在我们的示例中,Book 和 Author 通过 @ManyToMany 缔合关联,且都是“根”。

// new way

@Repository

interface Publishing {

@Find

Book book(String isbn);

@Find

Author author(String ssn);

@Insert

void publish(Book book);

@Insert

void create(Author author);

// query methods

...

}

现在,让我们了解仓库界面可能会声明的不同类型的函数,从最简单的类型开始。如果以下总结不够充分,你可以在 Jakarta 数据规范的第 4 章和相关注释的 Javadoc 中找到有关仓库的更多详细信息。

1.3. 默认方法

default 方法是你自己实现的方法,并没有什么特别的。

@Repository

interface Library {

default void hello() {

System.out.println("Hello, World!");

}

}

这看起来没什么用,至少不是在有某种方式从 default 方法与数据库交互时。为此,我们需要添加资源访问器方法。

1.4. 资源访问器方法

资源访问器方法是公开对基础实现类型的访问权限的方法。目前,Hibernate 数据仓库仅支持一种此类类型:StatelessSession。因此资源访问器方法只是任何返回 StatelessSession 的抽象方法。该方法的名称无关紧要。

StatelessSession session();

此方法返回支持仓库的 StatelessSession。

通常,从同一仓库的 default 方法调用资源访问器方法。

default void refresh(Book book) {

session().refresh(book);

}

这在我们需要直接访问 StatelessSession 以便利用 Hibernate 的全部功能时非常有用。

当我们需要延迟获取关联时,资源访问器方法也很有用。

library.session().fetch(book.authors);

当然,在通常情况下,我们希望 Jakarta Data 负责与 StatelessSession 进行交互。

1.5. 生命周期方法

Jakarta Data 1.0 定义了四个内置生命周期注释,这些注释与 Hibernate StatelessSession 的基本操作完全匹配。

@Insert 映射到 insert(),

@Update 映射到 update(),

@Delete 映射到 delete(),且

@Save 映射到 upsert()。

StatelessSession 的基本操作(insert()、update()、delete() 和 upsert())没有与之匹配的 CascadeType,因此,这些操作永远不会级联到关联的实体。

生命周期方法通常接受实体类型的一个实例,并且通常被声明为 void。

@Insert

void add(Book book);

另外,它还可以接受一个实体列表或数组。(可变参数被视为一个数组。)

@Insert

void add(Book... books);

Jakarta Data 的未来版本可能会扩展内置生命周期注释的列表。具体而言,我们希望能够添加 @Persist、@Merge、@Refresh、@Lock 和 @Remove,以映射到 EntityManager 的基本操作。

如果仅仅能做到这样,存储库就毫无用处。当我们开始使用 Jakarta Data 来表示查询时,其优势才开始真正显现。

1.6. 自动查询方法

自动查询方法通常使用 @Find 进行注释。最简单的自动查询方法是通过唯一标识符检索实体实例的一个方法。

@Find

Book book(String isbn);

参数的名称表明这是一个通过主键进行查找(isbn 字段在 Book 中使用 @Id 进行注释),因此,这个方法将被实现为调用 StatelessSession 的 get() 方法。

如果参数名称与返回实体类型的任何字段不匹配,或者如果参数类型与匹配字段的类型不匹配,HibernateProcessor 会在编译时报告一个有用的错误。这是我们第一次了解到使用 Jakarta Data 存储库和 Hibernate 的好处。

如果数据库中没有具有给定 isbn 的 Book,该方法将抛出 EmptyResultException。如果有两种方法可以解决这种情况

声明方法以返回 Optional,或者

使用 @jakarta.annotation.Nullable 对方法进行注释。

第一个选项得到规范的认可

@Find

Optional book(String isbn);

第二个选项是 Hibernate 提供的扩展

@Find @Nullable

Book book(String isbn);

自动查询方法可能会返回多个结果。在这种情况下,返回类型必须是实体类型的数组或列表。

@Find

List book(String title);

通常,自动查询方法的参数必须与实体的字段完全匹配。但是,Hibernate 提供了 @Pattern 注释,以允许使用 like 进行“模糊”匹配。

@Find

List books(@Pattern String title);

此外,如果参数类型是实体字段类型的列表或数组,则得到的查询有一个 in 条件。

@Find

List books(String[] ibsn);

当然,一个自动查询方法可能有多个参数。

@Find

List book(@Pattern String title, Year yearPublished);

这种情况下,每个参数都必须与实体的对应字段相匹配。

参数名中的字符 _ 可用来导航关联

@Find

List booksPublishedBy(String publisher_name);

然而,一旦查询开始涉及多个实体,通常最好使用 注释查询方法。

@OrderBy 注释允许对结果排序。

@Find

@OrderBy("title")

@OrderBy("publisher.name")

List book(@Pattern String title, Year yearPublished);

这乍一看可能不怎么类型安全,但是——令人惊奇的是——@OrderBy 注释的内容在编译时会得到完全验证,如下所示。

自动查询方法对于非常简单的查询来说非常出色且方便。对于任何不太简单的查询,我们最好用 JPQL 编写查询。

1.7. 注解查询方法

注释查询方法使用以下方法声明

@Query 来自 Jakarta Data,或

@HQL 或 @SQL 来自 org.hibernate.annotations.processing。

定义 @Query 注释可以接受 JPQL、JDQL 或介于两者之间的任何内容。在 Hibernate Data Repositories 中,它接受任意的 HQL。

没有充分的理由不优先使用 @HQL 而不是 @Query。存在此注释是因为此处描述的功能早于 Jakarta Data 的存在。

考虑以下示例

@Query("where title like :pattern order by title, isbn")

List booksByTitle(String pattern);

您可能会注意到:

from 子句在 JDQL 中不需要,并且从仓库方法的返回类型推断出来。

自 Jakarta Persistence 3.2 以来,JPQL 中也不需要 select 原因或实体别名(标识变量),最终将 HQL 的一个非常古老的功能标准化。

这样就可以以非常简洁的形式编写简单查询。

方法参数自动匹配查询的序数或命名参数。在前面的示例中,pattern 匹配 :pattern。在以下变体中,第一个方法参数匹配 ?1。

@Query("where title like ?1 order by title, isbn")

List booksByTitle(String pattern);

您可能想象 @Query 注释中指定的 JPQL 查询不能在编译时验证,但事实并非如此。HibernateProcessor 不仅能够验证查询的语法,它甚至能够完全对查询进行类型检查。这比将字符串传递给 EntityManager 的 createQuery() 方法好得多,这可能是将 Jakarta Data 与 Hibernate 一起使用的首要原因。

当查询返回多个对象时,最好的做法是将每个结果打包为 Java record 类型的一个实例。例如,我们可能定义一个 record,其中包含 Book 和 Author 的一些字段。

record AuthorBookSummary(String isbn, String ssn, String authorName, String title) {}

我们需要指定select子句中的值应打包为AuthorBookSummary的实例。JPQL 规范为此提供了select new构造。

@Query("select new AuthorBookRecord(b.isbn, a.ssn, a.name, b.title " +

"from Author a join books b " +

"where title like :pattern")

List summariesForTitle(@Pattern String pattern);

请注意,此处需要from子句,因为无法从存储库方法的返回类型推断查询实体类型。

由于这非常冗长,Hibernate 不需要使用select new或别名,并且允许我们编写

@Query("select isbn, ssn, name, title " +

"from Author join books " +

"where title like :pattern")

List summariesForTitle(@Pattern String pattern);

带注释的查询方法甚至可以执行update、delete或insert语句。

@Query("delete from Book " +

"where extract(year from publicationDate) < :year")

int deleteOldBooks(int year);

该方法必须声明为void,或返回int或long。返回值是受影响记录的数量。

最后,可以使用@SQL指定本机 SQL 查询。

@SQL("select title from books where title like :pattern order by title, isbn")

List booksByTitle(String pattern);

不幸的是,本机 SQL 查询无法在编译时验证,因此如果我们的 SQL 有任何问题,我们会在运行程序之前发现。

1.8. @By 和 @Param

查询方法通过名称将方法参数与实体字段或查询参数进行匹配。有时,这会带来不便,导致方法参数名称不够自然。让我们重新考虑上面已经看到的示例

@Find

List books(String[] ibsn);

此处,因为参数名称必须与Book的isbn字段匹配,我们无法将其称为复数形式isbns。

@By注释允许我们解决此问题

@Find

List books(@By("isbn") String[] ibsns);

当然,参数的名称和类型在编译时仍会被检查;尽管有字符串,但这里不会损失类型安全性。

@Param注释的作用要小得多,因为我们始终可以重命名 HQL 查询参数以匹配方法参数,或者在最坏的情况下,改用序数参数。

相关推荐

365体育app官方下载 揭秘直播间,为何你看到的假人现象如此普遍?
英国手机版365 山东泰山退赛风波:政治风波下的体育决策?
英国手机版365 口袋狼人杀怎么刷金币 狼人杀刷金币方法