了解 LazyCollectionOption.EXTRA

但最令人感兴趣的是 LazyCollectionOption.EXTRA 值。如果您将它指定为@LazyCollection注释的值,那么 Hibernate 将尽可能长时间地延迟加载集合的元素。

如果您尝试获取集合中的元素数量:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
int count = commetns.size();

然后对于所有这些代码,Hibernate 将只执行一个查询:

SELECT COUNT(id) FROM comment WHERE user_id = 1;

但是,如果您想从集合中获取一条评论,例如第 3 条:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
Comment comment = commetns.get(3);

那么问题来了:Hibernate 应该如何在不将所有元素加载到内存的情况下知道该元素是第三个?

为了解决这个问题,建议在评论表中增加一列,用于存储评论在评论集合中的序号。为此,您还需要一个特殊的注释 - @OrderColumn

该解决方案如下所示:

@Entity
@Table(name=”user”)
class User {
   @Column(name=”id”)
   public Integer id;

   @OneToMany(cascade = CascadeType.ALL)
   @LazyCollection(LazyCollectionOption.EXTRA)
   @OrderColumn(name = "order_id")
   public List<Comment> comments;
}

LazyCollectionOption.EXTRA 的主要优点

当我们使用@ManyToMany注释指定它时,我们看到了 LazyCollectionOption.EXTRA 的最强优势。让我们以我们有一个员工和一个任务的旧案例为例,我们可以将许多任务分配给一个用户。

我们的 Java 类如下所示:

员工类

@Entity
@Table(name=”employee”)
class Employee {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="employee_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();

}

EmployeeTask 类

@Entity
@Table(name=”task”)
class EmployeeTask {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="task_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<Employee> employees = new HashSet<Employee>();

}

并向导演添加一个任务,你需要写这样的代码:

Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);

session.update(task);
session.flush();

因此,如果 Task 类中的 employees 字段具有 LazyCollectionOption.EXTRA 注释,则(Task 类的)employees 集合和(Employee 类的)task 集合将永远不会从数据库中加载

执行此代码时,只会将一条记录插入到 employee_task 服务表中,您还记得,它看起来像这样:

员工任务表
员工ID 任务编号
1个 1个
2个 2个
5个 3个
5个 4个
5个 5个
4个 7
6个 8个
4个 101

添加的行以绿色突出显示。要添加这一行,您不需要从数据库加载集合——Hibernate 不需要它。这正是 LazyCollectionOption.EXTRA 大大加快数据库工作的情况。

N+1问题

但是,当然,这种模式也有缺点。例如,LazyCollectionOption.EXTRA 注释生成N+1 Problem

如果您决定查看所有用户的评论:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
for (Comment comment : comments) {
    System.out.println(comment);
}

然后 Hibernate 将对每个 Comment 对象的单独请求执行。还有一个额外的查询来获取所有评论的数量。这会显着降低代码速度。

如果您的用户有 1000 条评论,那么 Hibernate 将对数据库进行 1001 次查询以执行此代码,尽管它可以只执行一次。如果您事先知道您将需要该类的所有对象。