什么是JPA——雅加达持久化API简介

203 阅读9分钟

你需要知道的关于Java编程工具和API的一切,包括代码和例子。

了解Java ORM标准,用于存储、访问和管理关系型或NoSQL数据库中的Java对象

作为一个规范,Jakarta Persistence API(以前的Java Persistence API)关注的是持久性,这松散地意味着Java对象比创建它们的应用程序更长久的任何机制。并非所有的Java对象都需要被持久化,但大多数应用程序都会持久化关键的业务对象。JPA规范让你定义哪些对象应该被持久化,以及它们在你的Java应用中如何被持久化。

就其本身而言,JPA不是一个工具或框架;相反,它定义了一套指导实施者的概念。虽然JPA的对象关系映射(ORM)模型最初是以Hibernate为基础的,但它后来又有了发展。同样,虽然JPA最初是用于关系型数据库的,但一些JPA的实现已经被扩展为用于NoSQL数据存储。一个支持JPA与NoSQL的流行框架是EclipseLink,这是JPA 3的参考实现。

在JPA中,你在Java代码和对象的领域中定义你的持久化规则,而JDBC需要你手动将代码转换为关系表,然后再返回。

流行的JPA实现,如Hibernate和EclipseLink现在支持JPA 3。从JPA 2迁移到JPA 3涉及到一些命名空间的变化,但除此之外,这些变化都是内在的性能提升。

JPA和Hibernate

由于它们交织在一起的历史,Hibernate和JPA经常被混为一谈。然而,就像Java Servlet规范一样,JPA已经催生了许多兼容的工具和框架。Hibernate只是众多JPA工具中的一个。

Hibernate由Gavin King开发,于2002年初首次发布,是一个Java的ORM库。King开发了Hibernate,作为实体豆的替代品来实现持久性。这个框架在当时非常流行,也非常需要,所以它的许多想法被采纳并编入了第一个JPA规范。

今天,Hibernate ORM是最成熟的JPA实现之一,并且仍然是Java中ORM的一个流行选择。截至目前,最新发布的Hibernate ORM 6,实现了JPA 2.2。其他的Hibernate工具包括Hibernate SearchHibernate ValidatorHibernate OGM,它支持NoSQL的领域模型持久化。

什么是Java ORM?

虽然它们的执行方式不同,但每个JPA实现都提供了某种ORM层。为了理解JPA和JPA兼容的工具,你需要对ORM有一个很好的掌握。

对象关系映射是一项任务--开发者有充分的理由避免手动操作。像Hibernate ORM或EclipseLink这样的框架将这项任务编入一个库或框架,即ORM层。作为应用架构的一部分,ORM层负责管理软件对象的转换,以便与关系数据库中的表和列进行交互。在Java中,ORM层转换Java类和对象,以便它们可以在关系型数据库中存储和管理。

默认情况下,被持久化的对象的名称成为表的名称,而字段成为列。一旦表被设置好,每个表行都对应于应用程序中的一个对象。对象映射是可配置的,但默认值往往是有效的,通过坚持使用默认值,你可以避免维护配置元数据。

图1说明了JPA和ORM层在应用开发中的作用:

JPA in the Java ORM layer.IDG

图1.JPA和Java ORM层。

配置Java ORM层

当你设置一个新项目来使用JPA时,你需要配置数据存储和JPA提供者。你将配置一个数据存储连接器来连接到你选择的数据库(SQL或NoSQL)。你还将包括并配置JPA提供者,它是一个框架,如Hibernate或EclipseLink。虽然你可以手动配置JPA,但许多开发者选择使用Spring的开箱即用支持。我们很快就会看看手动和基于Spring的JPA安装和设置。

Java中的数据持久性

从编程的角度来看,ORM层是一个适配器层:它将对象图的语言适应于SQL和关系表的语言。ORM层允许面向对象的开发者在不离开面向对象范式的情况下建立持久化数据的软件。

当你使用JPA时,你创建一个从数据存储到你的应用程序的数据模型对象的映射。你不需要定义对象如何被保存和检索,而是定义对象和数据库之间的映射,然后调用JPA来持久化它们。如果你使用的是关系型数据库,你的应用程序代码和数据库之间的大部分实际连接将由JDBC来处理。

作为一种规范,JPA提供了元数据注释,你可以用它来定义对象和数据库之间的映射。每个JPA实现都为JPA注释提供了自己的引擎。JPA规范还提供了PersistanceManagerEntityManager ,这些都是与JPA系统接触的关键点(在这里你的业务逻辑代码告诉系统如何处理映射的对象)。

为了使这一切更加具体,请看清单1,这是一个简单的数据类,用于为音乐家建模。

清单1.Java中的一个简单的数据类


public class Musician {
  private Long id;
  private String name;
  private Instrument mainInstrument;
  private ArrayList performances = new ArrayList();
  public Musician( Long id, String name){ /* constructor setters... */ }
  public void setName(String name){
      this.name = name;
  }
  public String getName(){
      return this.name;
  }
  public void setMainInstrument(Instrument instr){
      this.instrument = instr;
  }
  public Instrument getMainInstrument(){
      return this.instrument;
  }
  // ...Other getters and setters...
}

清单1中的Musician 类是用来保存数据的。它可以包含原始的数据,如姓名字段。它也可以持有与其他类的关系,如mainInstrumentperformances

Musician存在的原因是为了包含数据。这种类型的类有时被称为DTO,即数据传输对象。DTOs是软件开发的一个常见特征。虽然它们持有许多种类的数据,但它们不包含任何业务逻辑。保存数据对象是软件开发中一个无处不在的挑战。

使用JDBC的数据持久化

Musician 类的实例保存到关系型数据库的一种方法是使用JDBC库。JDBC是一个抽象层,可以让应用程序发布SQL命令,而不需要考虑底层数据库的实现。

清单2显示了你如何使用JDBC来持久化Musician 类。

清单2.JDBC插入一条记录


Musician georgeHarrison = new Musician(0, "George Harrison");
      String myDriver = "org.gjt.mm.mysql.Driver";
      String myUrl = "jdbc:mysql://localhost/test";
      Class.forName(myDriver);
      Connection conn = DriverManager.getConnection(myUrl, "root", "");
      String query = " insert into users (id, name) values (?, ?)";
      PreparedStatement preparedStmt = conn.prepareStatement(query);
      preparedStmt.setInt     (1, 0);
      preparedStmt.setString (2, "George Harrison");
      preparedStmt.setString (2, "Rubble");
      preparedStmt.execute();
      conn.close();
// Error handling removed for brevity

清单2中的代码是相当自我记录的。georgeHarrison 对象可以来自任何地方(前端提交、外部服务等),并设置了它的ID和名字字段。然后该对象上的字段被用来提供SQL插入语句的值。(PreparedStatement 类是JDBC的一部分,提供了一种安全地将数值应用于SQL查询的方法。)

虽然JDBC提供了手动配置带来的控制,但与JPA相比,它是很麻烦的。要修改数据库,你首先需要创建一个SQL查询,从你的Java对象映射到关系数据库中的表。然后,每当一个对象的签名发生变化时,你就必须修改该SQL。有了JDBC,维护SQL本身就成了一项任务。

使用JPA的数据持久化

现在考虑清单3,在那里我们使用JPA来持久化Musician 类。

清单3.用JPA持久化George Harrison


Musician georgeHarrison = new Musician(0, "George Harrison");
musicianManager.save(georgeHarrison);

清单3用一句话取代了清单2中的手动SQL,即entityManager.save() ,它指示JPA持久化对象。从那时起,框架会处理SQL转换,所以你永远不必离开面向对象的范式。

JPA中的元数据注释

清单3中的魔法是一个配置的结果,它是使用JPA的注解创建的。开发者使用注解来告知JPA哪些对象应该被持久化,以及它们应该如何被持久化。

清单4显示了带有一个JPA注解的Musician 类。

清单4.JPA的@Entity注解


@Entity
public class Musician {
  // ..class body
}

持久的对象有时被称为实体。将@Entity 附加到像Musician 这样的类上,可以通知JPA这个类和它的对象应该被持久化。

配置JPA

像大多数现代框架一样,JPA采用约定俗成的编码方式(也被称为约定俗成的配置),其中框架提供了基于行业最佳实践的默认配置。举个例子,一个名为Musician 的类将被默认映射到一个名为Musician的数据库表。

传统的配置是一种节省时间的方法,而且在许多情况下,它的效果也足够好。也可以定制你的JPA配置。举个例子,你可以使用JPA的@Table 注解来指定Musician 类应该被存储的表。

清单5.JPA的@Table注解


@Entity
@Table(name="musician")
public class Musician {
  // ..class body
}

清单5告诉JPA将实体(Musician 类)持久化到Musician表中。

主键

在JPA中,主键是用来唯一标识数据库中每个对象的字段。主键对于引用对象和其他实体的关系很有用。每当你在表中存储一个对象时,你也会指定字段作为其主键使用。

在清单6中,我们告诉JPA使用什么字段作为Musician'的主键。

清单6.指定主键


@Entity
public class Musician {
   @Id
   private Long id;

在这个例子中,我们使用JPA的@Id 注释来指定id 字段作为Musician 的主键。默认情况下,这种配置假设主键将由数据库来设置--例如,当字段被设置为自动递增时,表上的主键就会被自动递增。

JPA支持其他策略来生成对象的主键。它也有用于改变单个字段名的注释。一般来说,JPA足够灵活,可以适应你可能需要的任何持久化映射。

CRUD操作

一旦你将一个类映射到数据库表并建立了它的主键,你就拥有了在数据库中创建、检索、删除和更新该类所需的一切。调用entityManager.save() 将创建或更新指定的类,这取决于主键字段是否为空或适用于现有实体。调用entityManager.remove() ,将删除指定的类。

实体关系

简单地用一个主键字段来持久化一个对象,这只是问题的一半。JPA还允许你管理实体之间的关系。在表和对象中都可以有四种实体关系:

  1. 一对多
  2. 多对一
  3. 多对多
  4. 一对一

每种类型的关系都描述了一个实体如何与其他实体发生关系。例如,Musician 实体可以与Performance ,一个由ListSet 等集合代表的实体有一对多的关系