相关文章推荐

介绍

众所周知,程序的主要任务之一是存储和处理数据。在过去的美好时光,人们只是将数据存储在文件中。但是,一旦需要同时进行读取和编辑访问,当存在负载时(即多个请求同时到达),将数据简单地存储在文件中就成为问题。有关数据库解决什么问题以及如何解决问题的更多信息,我建议您阅读文章“ 数据库的结构 ”。这意味着我们决定将数据存储在数据库中。长期以来,Java 一直能够使用 JDBC API(Java 数据库连接)来处理数据库。您可以在此处阅读有关 JDBC 的更多信息:“ JDBC 或一切的开始 。” 但随着时间的推移,开发人员每次都需要编写相同类型和不必要的“维护”代码(所谓的样板代码),以执行在数据库中保存 Java 对象的琐碎操作,反之亦然,使用来自数据库的数据创建 Java 对象。数据库。于是,为了解决这些问题,ORM这样的概念就诞生了。 ORM——对象关系映射或翻译成俄语对象关系映射。 它是一种将数据库与面向对象编程语言的概念联系起来的编程技术。简单来说,ORM 是 Java 对象和数据库中的记录之间的连接: JPA:技术简介 - 2 ORM 本质上是 Java 对象可以表示为数据库中的数据(反之亦然)的概念。它以 JPA 规范——Java Persistence API 的形式体现。该规范已经是表达这一概念的 Java API 的描述。该规范告诉我们必须提供哪些工具(即我们可以通过哪些接口进行工作)才能根据 ORM 概念进行工作。以及如何使用这些资金。该规范没有描述工具的实现。这使得对一种规范使用不同的实现成为可能。你可以简化它,说规范是 API 的描述。JPA规范的文本可以在Oracle网站上找到:“ JSR 338:JavaTM Persistence API ”。因此,为了使用 JPA,我们需要一些使用该技术的实现。JPA 实现也称为 JPA 提供程序。 Hibernate 是最著名的 JPA 实现之一。因此,我建议考虑一下。

创建项目

由于 JPA 是关于 Java 的,因此我们需要一个 Java 项目。我们可以自己手动创建目录结构并自己添加必要的库。但是使用系统来自动化项目组装要方便和正确得多(即,本质上,这只是一个为我们管理项目组装的程序。创建目录,将必要的库添加到类路径等) .)。Gradle 就是这样的系统之一。您可以在这里阅读有关 Gradle 的更多信息:“ Gradle 简介 ”。众所周知,Gradle 功能(即它可以做的事情)是使用各种 Gradle 插件来实现的。让我们使用 Gradle 和“ Gradle Build Init Plugin ”插件。让我们运行命令: gradle init --type java-application Gradle 将为我们创建必要的目录结构,并在构建脚本中创建项目的基本声明性描述 build.gradle 。所以,我们有一个应用程序。我们需要考虑我们想要用我们的应用程序描述或建模什么。让我们使用一些建模工具,例如: app.quickdatabasediagrams.com JPA:技术简介 - 4 这里值得一提的是,我们所描述的是我们的“领域模型”。域是一个“主题领域”。一般来说,“domain”在拉丁语中是“占有”的意思。在中世纪,这是国王或封建领主拥有的地区的名称。在法语中,它变成了“domaine”这个词,简单地翻译为“区域”。因此,我们描述了我们的“领域模型”=“主题模型”。这个模型的每个元素都是某种“本质”,来自现实生活的东西。在我们的例子中,这些是实体:类别 ( Category )、主题 ( Topic )。让我们为实体创建一个单独的包,例如使用名称模型。让我们在其中添加描述实体的 Java 类。在 Java 代码中,此类实体是常规 POJO ,可能如下所示:
我们把类的内容复制过来,以此类推创建一个类 Topic 。他的不同之处仅在于他对自己所属类别的了解。因此,让我们 Topic 向类中添加一个类别字段和使用它的方法:
现在我们有了一个具有自己的域模型的 Java 应用程序。现在是时候开始连接到 JPA 项目了。 dependencies { implementation 'com.h2database:h2:1.4.199' 现在我们有了一个数据库。现在,我们可以向应用程序添加一个层,负责将 Java 对象映射到数据库概念(从 Java 到 SQL)。我们记得,为此我们将使用名为 Hibernate 的 JPA 规范实现: dependencies { implementation 'com.h2database:h2:1.4.199' implementation 'org.hibernate:hibernate-core:5.4.2.Final' 现在我们需要配置JPA。如果我们阅读规范和“8.1 持久性单元”部分,我们就会知道持久性单元是配置、元数据和实体的某种组合。为了使 JPA 工作,您需要在配置文件中描述至少一个持久单元,称为 persistence.xml . 其位置在规范章节“8.2 持久性单元包装”中描述。根据本节,如果我们有Java SE环境,那么我们必须将其放在根目录的META-INF目录中。 让我们从“JPA规范”章节中给出的示例中复制内容 8.2.1 persistence.xml file
但这还不够。我们需要告诉我们的 JPA 提供商是谁,即 实现 JPA 规范的人:
现在让我们添加设置 ( properties )。其中一些(以 开头 javax.persistence )是标准 JPA 配置,并在 JPA 规范的“8.2.1.9 属性”部分中进行了描述。一些配置是特定于提供者的(在我们的例子中,它们影响 Hibernate 作为 Jpa 提供者。我们的设置块将如下所示:
现在我们有一个与 JPA 兼容的配置 persistence.xml ,有一个 JPA 提供程序 Hibernate,有一个 H2 数据库,还有 2 个类是我们的域模型。让我们最终让这一切发挥作用。在目录中 /test/java ,我们的 Gradle 友好地生成了一个用于单元测试的模板,并将其命名为 AppTest。让我们使用它吧。正如 JPA 规范的“7.1 持久性上下文”一章中所述,JPA 世界中的实体存在于称为持久性上下文的空间中。但我们不直接使用持久性上下文。为此,我们使用 Entity Manager “实体管理器”。他知道上下文以及那里生活着什么实体。我们与“om”互动 Entity Manager 。那么剩下的就是了解我们在哪里可以得到这个 Entity Manager ?根据 JPA 规范的“7.2.2 获取应用程序管理的实体管理器”一章,我们必须使用 EntityManagerFactory . 因此,让我们用JPA规范来武装自己,并以“7.3.2在Java SE环境中获取实体管理器工厂”一章中的示例为例,并将其格式化为简单的单元测试的形式:
此测试已经显示错误“无法识别的 JPA persistence.xml XSD 版本”。原因是 persistence.xml 您需要正确指定要使用的模式,如“8.3 persistence.xml Schema”部分中的 JPA 规范中所述:
此外,元素的顺序也很重要。因此, provider 必须在列出类之前指定它。之后,测试将成功运行。我们已经完成了 JPA 的直接连接。在我们继续之前,让我们考虑一下剩下的测试。我们的每个测试都需要 EntityManager . 让我们确保每个测试 EntityManager 在执行开始时都有自己的测试。另外,我们希望数据库每次都是新的。由于我们使用 inmemory 该选项,因此关闭就足够了 EntityManagerFactory 。创建 Factory 是一项昂贵的操作。但对于测试来说这是合理的。JUnit 允许您指定在每个测试执行之前(Before)和之后(After)执行的方法:
现在,在执行任何测试之前,将创建一个新的测试 EntityManagerFactory ,这将需要创建一个新的数据库,因为 hibernate.hbm2ddl.auto 有这个意思 create 。从新工厂我们将得到一个新工厂 EntityManager

实体

我们记得,我们之前创建了描述域模型的类。我们已经说过,这些是我们的“本质”。这是我们将使用 来管理的实体 EntityManager 。让我们编写一个简单的测试来保存类别的本质:
但这个测试不会立即起作用,因为…… 我们将收到各种错误,这将帮助我们了解实体是什么:

Unknown entity: hibernate.model.Category
为什么 Hibernate 不明白 Category 这是什么 entity ?问题是实体必须根据 JPA 标准进行描述。
实体类必须使用annotation进行注释 @Entity ,如JPA规范的“2.1实体类”一章中所述。

No identifier specified for entity: hibernate.model.Category
实体必须具有可用于区分一条记录与另一条记录的唯一标识符。
根据JPA规范的“2.4主键和实体身份”一章,“每个实体都必须有一个主键”,即 每个实体都必须有一个“主键”。这样的主键必须通过注解来指定 @Id

ids for this class must be manually assigned before calling save()
ID 必须来自某个地方。可以手动指定,也可以自动获取。
因此,如“11.2.3.3GenerateValue”和“11.1.20GenerateValue注解”章节所示,我们可以指定注解 @GeneratedValue

因此,为了使类别类成为一个实体,我们必须进行以下更改:
另外,注释还 @Id 指出了要使用哪一个 Access Type 。您可以在 JPA 规范的“2.3 访问类型”部分中阅读有关访问类型的更多信息。简而言之,因为... 我们 @Id 在字段 ( field ) 上面指定了,那么访问类型将为 default field-based ,而不是 property-based 。因此,JPA 提供程序将直接从字段读取并存储值。如果我们放置 @Id 在 getter 之上,那么 property-based 将使用访问权限,即 通过 getter 和 setter。运行测试时,我们还可以看到哪些请求发送到数据库(感谢选项 hibernate.show_sql )。但是保存时,我们看不到任何 insert 。原来我们其实什么都没保存?JPA 允许您使用以下方法同步持久性上下文和数据库 flush
entityManager.




    
flush();
但如果我们现在执行,我们会得到一个错误: no transaction is inprogress 。现在是时候了解 JPA 如何使用事务了。

JPA 交易

我们记得,JPA 基于持久性上下文的概念。这是实体居住的地方。我们通过 EntityManager . 当我们执行命令时 persist ,我们将实体放置在上下文中。更准确地说,我们告诉 EntityManager 您需要这样做。但这个上下文只是一些存储区域。它有时甚至被称为“一级缓存”。但需要连接数据库。该命令 flush 之前因错误而失败,它将持久性上下文中的数据与数据库同步。但这需要运输,而这种运输就是一种交易。JPA 中的事务在规范的“7.5 控制事务”部分中进行了描述。JPA中有一个特殊的API用于使用事务:
我们需要将事务管理添加到我们的代码中,该代码在测试之前和之后运行:
添加后,我们将在插入日志中看到之前没有的 SQL 表达式: EntityManager 事务中累积的更改已提交(确认并保存)在数据库中。现在让我们尝试寻找我们的本质。让我们创建一个测试来按 ID 搜索实体:
在这种情况下,我们将收到之前保存的实体,但我们不会在日志中看到 SELECT 查询。一切都基于我们所说的:“实体经理,请给我找到 ID=1 的类别实体。” 实体管理器首先在其上下文中查找(使用一种缓存),只有当没有找到时,它才会在数据库中查找。值得将ID更改为2(没有这样的事情,我们只保存了1个实例),我们将看到 SELECT 请求出现。因为在上下文中没有找到实体,并且 EntityManager 数据库正在尝试查找实体,所以我们可以使用不同的命令来控制上下文中实体的状态。实体从一种状态到另一种状态的转变称为实体的生命周期 lifecycle

实体生命周期

实体的生命周期在JPA规范的“3.2实体实例的生命周期”一章中描述。因为 实体生活在一个上下文中并受 控制 EntityManager ,那么他们说实体受到控制,即 管理。让我们看一下实体生命的各个阶段:
这是一个巩固它的图表: Topic 让我们看一下描述主题的实体。 Topic 对于态度我们能说什么 Category ?许多人 Topic 都属于一个类别。因此,我们需要一个协会 ManyToOne 。让我们在 JPA 中表达这种关系:
关于同一主题的一个很好的答案可以在这里阅读:“ 像我五岁一样解释 ORM oneToMany、manyToMany 关系 ”。如果一个类别与主题有联系 ToMany ,那么每个主题只能有一个类别,则为 One ,否则为 Many 。所以 Category 所有主题的列表将如下所示:
我们不要忘记本质上 Category 编写一个 getter 来获取所有主题的列表:
自动跟踪双向关系是一件非常困难的事情。因此,JPA 将这个责任转移给了开发人员。这对我们来说意味着,当我们 Topic 与 建立实体关系时 Category ,我们必须自己保证数据的一致性。这很简单:
让我们编写一个简单的测试来检查:
映射是一个完全独立的主题。作为本次审查的一部分,值得了解这是通过什么方式实现的。您可以在此处阅读有关映射的更多信息:
  • 终极指南 – JPA 和 Hibernate 的关联映射 ”。
  • JPA 和对象之间的关系
  • Hibernate 中的相关实体
  • JPQL

    JPA 引入了一个有趣的工具 - Java 持久性查询语言中的查询。这种语言与 SQL 类似,但使用 Java 对象模型而不是 SQL 表。让我们看一个例子:
    正如我们所看到的,在查询中我们使用了对实体 Category 而不是表的引用。而且还是这个实体的领域上 title 。JPQL 提供了许多有用的功能,值得单独撰写一篇文章。更多详细信息可以在评论中找到:
  • 使用 JPA 和 Hibernate 进行 JPQL 查询的终极指南
  • 标准API

    最后,我想谈谈 Criteria API。JPA 引入了动态查询构建工具。使用 Criteria API 的示例:
    这个例子相当于执行请求“ SELECT c FROM Category c ”。 Criteria API 是一个强大的工具。你可以在这里读更多关于它的内容:
  • JPA 标准 API 查询
  • JPA标准
  • JPA 2.0 中的动态类型安全查询
  • 结论

    正如我们所看到的,JPA 提供了大量的功能和工具。他们每个人都需要经验和知识。即使在 JPA 审查的框架内,也不可能提及所有内容,更不用说详细的深入探讨了。但我希望读完它之后,能够更清楚什么是 ORM 和 JPA、它们如何工作以及可以用它们做什么。好吧,对于零食我提供了各种材料:
  • JPA - 简介
  • 关于 Java 的思考 - Hibernate 初学者
  • 关于 Java 的思考 - Hibernate 技巧
  • 高级 JPA 主题
  • #维亚切斯拉夫 JavaRush 是一个从零开始学习 Java 语言编程的在线课程。本课程是初学者掌握 Java 语言的绝佳方式。它包含 1200 多个可即时验证的任务,以及基本范围内的 Java 基础理论。为了帮助你在教育上取得成功,我们实现了一组激励功能:小测验、编码项目以及有关高效学习和 Java 语言开发人员职业方面的内容。
     
    推荐文章