jpa和hibernate
本教程的前半部分介绍了Java Persistence API的基础知识,并向您展示了如何使用Hibernate 5.3.6和Java 8配置JPA应用程序。如果您已阅读该教程并研究了其示例应用程序,那么您将了解在JPA中建模JPA实体和多对一关系。 您还进行了一些使用JPA查询语言(JPQL)编写命名查询的练习。
在本教程的后半部分,我们将更深入地介绍JPA和Hibernate。 您将学习如何在Movie和SuperHero实体之间建立多对多关系的模型,为这些实体设置单独的存储库,以及将实体持久保存到H2内存数据库中 。 您还将了解有关JPA中级联操作的作用的更多信息,并获得有关为数据库中的实体选择CascadeType策略的技巧。 最后,我们将整理一个可以在IDE或命令行中运行的应用程序。
本教程侧重于JPA基础知识,但请务必查看这些Java技巧,这些技巧在JPA中引入了更多高级主题:
JPA中的多对多关系
多对多关系定义了实体, 关系的两端都可以相互引用。 对于我们的示例,我们将为电影和超级英雄建模。 与第1部分中的作者和书籍示例不同,一部电影可以具有多个超级英雄,并且一个超级英雄可以出现在多个电影中。 我们的超级英雄Ironman和Thor都出现在两部电影《复仇者联盟》和《复仇者联盟:无限战争》中。
要使用JPA对这种多对多关系进行建模,我们将需要三个表:
- 电影
- 超级英雄
- SUPERHERO_MOVIES
图1显示了具有三个表的域模型。
史蒂文·海恩斯
图1.电影和超级英雄的域模型
需要注意的是SuperHero_Movies是之间的连接表 Movie和SuperHero表。 在JPA中,联接表是一种特殊的表,它促进了多对多关系。
单向还是双向?
在JPA中,我们使用@ManyToMany批注对多对多关系进行建模。 这种类型的关系可以是单向或双向的:
- 在单向关系中,关系中只有一个实体指向另一个实体。
- 在双向关系中,两个实体都指向彼此。
我们的示例是双向的,这意味着电影指向其所有超级英雄,而超级英雄则指向其所有电影。 在双向多对多关系中,一个实体拥有该关系,而另一个实体映射到该关系。 我们使用mappedBy的属性@ManyToMany注解来创建此映射。
清单1显示了SuperHero类的源代码。
清单1. SuperHero.java
package com.geekcap.javaworld.jpa.model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@Entity
@Table(name = "SUPER_HERO")
public class SuperHero {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinTable(
name = "SuperHero_Movies",
joinColumns = {@JoinColumn(name = "superhero_id")},
inverseJoinColumns = {@JoinColumn(name = "movie_id")}
)
private Set<Movie> movies = new HashSet<>();
public SuperHero() {
}
public SuperHero(Integer id, String name) {
this.id = id;
this.name = name;
}
public SuperHero(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Movie> getMovies() {
return movies;
}
@Override
public String toString() {
return "SuperHero{" +
"id=" + id +
", name='" + name + '\'' +
", movies='" + movies.stream().map(Movie::getTitle).collect(Collectors.toList()) + '\'' +
'}';
}
}
SuperHero类具有几个批注,应该从第1部分开始熟悉:
-
@Entity将SuperHero标识为JPA实体。 -
@Table将SuperHero实体映射到“ SUPER_HERO”表。
另请注意Integer id字段,该字段指定将自动生成表的主键。
接下来,我们将看看@ManyToMany和@JoinTable批注。
提取策略
@ManyToMany批注中需要注意的事情是我们如何配置获取策略 ,该策略可以是懒惰的或渴望的。 在这种情况下,我们将fetch设置为EAGER ,以便从数据库检索SuperHero ,还将自动检索其所有对应的Movie 。
如果我们选择执行LAZY提取,则仅在特定访问时才检索每个Movie 。 仅当将SuperHero连接到EntityManager ,才可以进行延迟获取; 否则,访问超级英雄的电影将引发异常。 我们希望能够按需访问超级英雄的电影,因此在这种情况下,我们选择EAGER提取策略。
CascadeType.PERSIST
级联操作定义了超级英雄及其对应电影如何在数据库中持久化以及如何在数据库中持久化。 有许多级联类型配置可供选择,我们将在本教程的后面部分详细讨论它们。 现在,仅需注意,我们已经将CascadeType.PERSIST设置为cascade属性,这意味着当我们保存超级英雄时,还将保存其电影。
联接表
JoinTable是一个类,可促进SuperHero和Movie之间的多对多关系。 在此类中,我们定义了将存储SuperHero和Movie实体的主键的表。
清单1指定表名称为SuperHero_Movies 。 连接列将为superhero_id , 反向连接列将为movie_id 。 SuperHero实体拥有该关系,因此将使用SuperHero的主键填充join列。 然后,反向联接列将引用关系另一侧的实体Movie 。
根据清单1中的这些定义,我们期望创建一个名为SuperHero_Movies的新表。 该表将具有两列: superhero_id ,它引用了SUPERHERO表的id列; movie_id ,它引用了MOVIE表的id列。
电影课
清单2显示了Movie类的源代码。 回想一下,在双向关系中,一个实体拥有该关系(在本例中为SuperHero ),而另一个实体映射到该关系。 清单2中的代码包括应用于Movie类的关系映射。
清单2. Movie.java
package com.geekcap.javaworld.jpa.model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "MOVIE")
public class Movie {
@Id
@GeneratedValue
private Integer id;
private String title;
@ManyToMany(mappedBy = "movies", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private Set<SuperHero> superHeroes = new HashSet<>();
public Movie() {
}
public Movie(Integer id, String title) {
this.id = id;
this.title = title;
}
public Movie(String title) {
this.title = title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Set<SuperHero> getSuperHeroes() {
return superHeroes;
}
public void addSuperHero(SuperHero superHero) {
superHeroes.add(superHero);
superHero.getMovies().add(this);
}
@Override
public String toString() {
return "Movie{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
以下属性应用于清单2中的@ManyToMany注释:
-
mappedBy引用管理多对多关系的SuperHero类上的字段名称。 在这种情况下,它引用了电影领域,我们在清单1与相应的定义JoinTable。 -
cascade配置为CascadeType.PERSIST,这意味着在保存Movie时,还应保存其对应的SuperHero实体。 -
fetch告诉EntityManager它应该热切地检索电影的超级英雄:当加载Movie,它还应该加载所有对应的SuperHero实体。
关于Movie类的其他注意事项是其addSuperHero()方法。
为持久性配置实体时,仅向电影添加超级英雄是不够的。 我们还需要更新关系的另一端。 这意味着我们需要将电影添加到超级英雄。 如果正确配置了关系的两边,以使影片引用了超级英雄,而超级英雄引用了影片,那么联接表也将被正确填充。
我们已经定义了两个实体。 现在,让我们看一下将用于在数据库之间进行持久存储的存储库。
小费! 摆在桌子两边
仅设置关系的一侧,保留实体,然后观察联接表为空是一个常见的错误。 设置双方关系将解决此问题。
JPA储存库
我们可以直接在示例应用程序中实现所有持久性代码,但是创建存储库类使我们能够将持久性代码与应用程序代码分开 。 就像我们在第1部分中对“图书与作者”应用程序所做的一样,我们将创建一个EntityManager ,然后使用它来初始化两个存储库,每个存储库用于持久化的每个实体。
清单3显示了MovieRepository类的源代码。
清单3. MovieRepository.java
package com.geekcap.javaworld.jpa.repository;
import com.geekcap.javaworld.jpa.model.Movie;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class MovieRepository {
private EntityManager entityManager;
public MovieRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public Optional<Movie> save(Movie movie) {
try {
entityManager.getTransaction().begin();
entityManager.persist(movie);
entityManager.getTransaction().commit();
return Optional.of(movie);
} catch (Exception e) {
e.printStackTrace();
}
return Optional.empty();
}
public Optional<Movie> findById(Integer id) {
Movie movie = entityManager.find(Movie.class, id);
return movie != null ? Optional.of(movie) : Optional.empty();
}
public List<Movie> findAll() {
return entityManager.createQuery("from Movie").getResultList();
}
public void deleteById(Integer id) {
// Retrieve the movie with this ID
Movie movie = entityManager.find(Movie.class, id);
if (movie != null) {
try {
// Start a transaction because we're going to change the database
entityManager.getTransaction().begin();
// Remove all references to this movie by superheroes
movie.getSuperHeroes().forEach(superHero -> {
superHero.getMovies().remove(movie);
});
// Now remove the movie
entityManager.remove(movie);
// Commit the transaction
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
MovieRepository使用EntityManager初始化,然后将其保存到成员变量中以在其持久性方法中使用。 我们将考虑每种方法。
持久性方法
让我们回顾MovieRepository的持久性方法,看看它们如何与EntityManager的持久性方法交互。
jpa和hibernate
本文深入探讨了Java Persistence API (JPA) 和 Hibernate 中的多对多关系建模,通过电影和超级英雄的例子,讲解了如何在实体间建立多对多关系,配置存储库,并使用JPA查询语言进行数据操作。

被折叠的 条评论
为什么被折叠?



