jpa和hibernate
Java Persistence API(JPA)是一种Java规范,可在关系数据库和面向对象的编程之间架起桥梁。 这个由两部分组成的教程介绍了JPA,并说明了如何将Java对象建模为JPA实体,如何定义实体关系以及如何在Java应用程序中将JPA的EntityManager
和Repository模式一起使用。
请注意,本教程使用Hibernate作为JPA提供程序。 大多数概念可以扩展到其他Java持久性框架。
什么是JPA?
请参阅“ 什么是JPA?Java Persistence API简介 ”以了解JPA和相关框架(包括EJB 3.0)的发展。 和JDBC。
JPA中的对象关系
自1970年代以来,关系数据库已经作为一种存储程序数据的手段而存在。 尽管当今的开发人员有很多关系数据库的替代方案 ,但是这种类型的数据库是可伸缩的并且易于理解,并且仍广泛用于小型和大型软件开发中。
关系数据库上下文中的Java对象定义为实体 。 实体放置在表中,在表中它们占据列和行。 程序员使用外键和联接表来定义实体之间的关系-即一对一,一对多和多对多关系。 我们还可以使用SQL(结构化查询语言)使用外键约束来检索单个表和多个表中的数据并与之交互。 关系模型是扁平的,但是开发人员可以编写查询来检索数据并从该数据构造对象。
对象关系阻抗不匹配
您可能对术语“ 对象关系阻抗不匹配”很熟悉,这是将数据对象映射到关系数据库所面临的挑战。 之所以发生这种不匹配,是因为面向对象的设计不限于一对一,一对多和多对多的关系。 相反,在面向对象的设计中,我们考虑对象,它们的属性和行为以及对象之间的关系。 封装和继承是两个示例:
- 如果一个对象包含另一个对象,我们可以通过封装 -一个has-a关系来定义。
- 如果一个对象是另一个对象的特化,我们通过继承 -is -a关系来定义。
关联,聚合,组成,抽象,泛化,实现和依赖项都是面向对象的编程概念,可能很难映射到关系模型。
ORM:对象关系映射
面向对象的设计与关系数据库建模之间的不匹配导致了专门为对象关系映射(ORM)开发的一类工具。 Hibernate,EclipseLink和iBatis等ORM工具将关系数据库模型(包括实体及其关系)转换为面向对象的模型。 这些工具中有许多是在JPA规范之前存在的,但是没有标准,它们的功能就取决于供应商。
Java Persistence API(JPA)于2006年作为EJB 3.0的一部分首次发布,它提供了一种注释对象的标准方法,以便可以将它们映射并存储在关系数据库中。 该规范还定义了与数据库交互的通用结构。 拥有适用于Java的ORM标准可为供应商实现带来一致性,同时还具有灵活性和附加功能。 例如,虽然原始JPA规范适用于关系数据库,但某些供应商实现已扩展了JPA以用于NoSQL数据库。
JPA的演变
JPA的第一个版本1.0版于2006年通过Java社区流程(JCP)作为Java规范请求(JSR)220发布。版本2.0(JSR 317)于2009年发布,版本2.1(JSR 338)于2013年发布,并在2017年发布了2.2版(JSR 338的维护版本)。已选择JPA 2.2包含在Jakarta EE中并正在进行开发。
JPA入门
Java Persistence API是一种规范,而不是一种实现:它定义了一个通用抽象,您可以在代码中使用该抽象来与ORM产品进行交互。 本节回顾了JPA规范的一些重要部分。
您将学习如何:
- 在数据库中定义实体,字段和主键。
- 在数据库中的实体之间创建关系。
- 使用
EntityManager
及其方法。
定义实体
为了定义实体,您必须创建一个使用@Entity
注释进行注释的类。 @Entity
批注是标记批注 ,用于发现持久性实体。 例如,如果您要创建一个图书实体,则可以按以下方式对其进行注释:
@Entity
public class Book {
...
}
默认情况下,该实体将映射到Book
表(由给定的类名确定)。 如果您想将此实体映射到另一个表(以及可选的特定模式),则可以使用@Table
注释来实现。 这是将Book
类映射到BOOKS表的方法:
@Entity
@Table(name="BOOKS")
public class Book {
...
}
如果BOOKS表位于PUBLISHING模式中,则可以将该模式添加到@Table
批注中:
@Table(name="BOOKS", schema="PUBLISHING")
将字段映射到列
将实体映射到表后,下一个任务是定义其字段。 字段在类中定义为成员变量,每个字段的名称都映射到表中的列名称。 您可以使用@Column
批注覆盖此默认映射,如下所示:
@Entity
@Table(name="BOOKS")
public class Book {
private String name;
@Column(name="ISBN_NUMBER")
private String isbn;
...
}
在此示例中,我们接受了name
属性的默认映射,但为isbn
属性指定了自定义映射。 name
属性将映射到name列,但是isbn
属性将映射到ISBN_NUMBER列。
@Column
批注允许我们定义字段/列的其他属性,包括长度,是否可以为空,是否必须唯一,精度和小数位数(如果是十进制值),是否可插入和可更新以及等等。
指定主键
关系数据库表的要求之一是它必须包含主键或唯一标识数据库中特定行的键。 在JPA中,我们使用@Id
批注将字段指定为表的主键。 主键必须是Java基本类型,基本包装(例如Integer
或Long
, String
, Date
, BigInteger
或BigDecimal
。
在此示例中,我们将id
属性(即Integer
映射到BOOKS表中的ID列:
@Entity
@Table(name="BOOKS")
public class Book {
@Id
private Integer id;
private String name;
@Column(name="ISBN_NUMBER")
private String isbn;
...
}
也可以将@Id
注释与@Column
注释结合使用,以覆盖主键的列名映射。
实体之间的关系
现在您知道了如何定义实体,让我们看看如何在实体之间创建关系。 JPA定义了四个用于定义实体的注释:
-
@OneToOne
-
@OneToMany
-
@ManyToOne
-
@ManyToMany
一对一关系
@OneToOne
批注用于定义两个实体之间的一对一关系。 例如,您可能具有一个User
实体,其中包含用户的名称,电子邮件和密码,但是您可能希望在单独的UserProfile
实体中维护有关该用户的其他信息(例如年龄,性别和喜欢的颜色)。 @OneToOne
注释有助于以这种方式分解数据和实体。
下面的User
类具有一个UserProfile
实例。 UserProfile
映射到单个User
实例。
@Entity
public class User {
@Id
private Integer id;
private String email;
private String name;
private String password;
@OneToOne(mappedBy="user")
private UserProfile profile;
...
}
@Entity
public class UserProfile {
@Id
private Integer id;
private int age;
private String gender;
private String favoriteColor;
@OneToOne
private User user;
...
}
JPA提供程序使用UserProfile
的user
字段将UserProfile
映射到User
。 映射在@OneToOne
批注中的mappedBy
属性中指定。
一对多和多对一关系
@OneToMany
和@ManyToOne
注释可促进同一关系的双方。 考虑一个示例,其中一Book
只能有一个Author
,但一个Author
可能有很多书。 Book
实体将与Author
定义一个@ManyToOne
关系,而Author
实体将与Book
定义一个@OneToMany
关系。
@Entity
public class Book {
@Id
private Integer id;
private String name;
@ManyToOne
@JoinColumn(name="AUTHOR_ID")
private Author author;
...
}
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
private String name;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
...
}
在这种情况下, Author
类维护该Author
编写的所有书籍的列表,而Book
类维护对其单个作者的引用。 另外, @JoinColumn
指定Book
表中列的名称,以存储Author
的ID。
多对多关系
最后, @ManyToMany
注释促进了实体之间的多对多关系。 这是Book
实体具有多个Author
的情况:
@Entity
public class Book {
@Id
private Integer id;
private String name;
@ManyToMany
@JoinTable(name="BOOK_AUTHORS",
joinColumns=@JoinColumn(name="BOOK_ID"),
inverseJoinColumns=@JoinColumn(name="AUTHOR_ID"))
private Set<Author> authors = new HashSet<>();
...
}
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany(mappedBy = "author")
private Set<Book> books = new HashSet<>();
...
}
在此示例中,我们创建一个具有两列的新表BOOK_AUTHORS
: BOOK_ID
和AUTHOR_ID
。 使用joinColumns
和inverseJoinColumns
属性可以告诉您的JPA框架如何以多对多关系映射这些类。 Author
类中的@ManyToMany
注释引用Book
类中用于管理关系的字段。 即authors
属性。
这是一个相当复杂的主题的快速演示。 在下一篇文章中,我们将进一步研究@JoinTable
和@JoinColumn
批注。
使用EntityManager
EntityManager
是在JPA中执行数据库交互的类。 它通过名为persistence.xml
的配置文件初始化。 该文件位于CLASSPATH
的META-INF
文件夹中,该文件夹通常打包在JAR或WAR文件中。 persistence.xml
文件包含:
- 名为“持久性单元”,它指定您正在使用的持久性框架,例如Hibernate或EclipseLink。
- 属性的集合,指定如何连接到数据库以及持久性框架中的所有自定义项。
- 项目中实体类的列表。
让我们来看一个例子。
配置EntityManager
首先,我们使用从Persistence
类检索的EntityManagerFactory
创建一个EntityManager
:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books");
EntityManager entityManager = entityManagerFactory.createEntityManager();
在这种情况下,我们创建了一个连接到“ Books”持久性单元的EntityManager
,我们已在persistence.xml
文件中对其进行了配置。
EntityManager
类定义我们的软件如何通过JPA实体与数据库进行交互。 以下是EntityManager
使用的一些方法:
-
find
通过其主键检索实体。 -
createQuery
创建一个Query
实例,该实例可用于从数据库中检索实体。 -
createNamedQuery
加载一个持久化实体之一内@NamedQuery
批注中定义的Query
。 命名查询提供了一种干净的机制,用于在将执行查询的持久性类的定义中集中化JPA查询。 -
getTransaction
定义要在数据库交互中使用的EntityTransaction
。 就像数据库事务一样,您通常将开始事务,执行操作,然后提交或回滚事务。 使用getTransaction()
方法可以在EntityManager
级别(而不是数据库getTransaction()
访问此行为。 -
merge()
将一个实体添加到持久性上下文中,以便在提交事务时,该实体将被持久化到数据库中。 使用merge()
,不管理对象。 -
persist
将实体添加到持久性上下文中,以便在提交事务时将实体持久化到数据库中。 使用persist()
,将管理对象。 -
refresh
从数据库刷新当前实体的状态。 -
flush
将持久性上下文的状态与数据库同步。
不必担心一次集成所有这些方法。 您将通过直接使用EntityManager
来了解它们,我们将在下一部分中做更多的工作。
jpa和hibernate