Skip to content
Go back

JPA的1+N问题

Published:  at  01:24 PM

1. 什么是 JPA 1 + N 问题

在使用 Java Persistence API (JPA) 进行数据库查询时,1 + N 问题是一个常见的性能问题。“1” 代表一次主查询,而 “N” 代表主查询结果集中每一条记录都要执行一次额外的查询。

具体来说,当你通过 JPA 执行一个查询获取实体列表,并且这些实体关联了其他实体(例如,通过 @ManyToOne@OneToOne 等注解),而 JPA 默认采用懒加载(Lazy Loading)策略时,在访问关联实体时就会触发额外的查询。

2. 示例场景

假设你有两个实体类:Order(订单)和 Customer(客户),一个订单对应一个客户,关系是 @ManyToOne

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    private Customer customer;

    // getters and setters
}

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // getters and setters
}

现在,你要查询所有订单,并打印出每个订单对应的客户名称:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("yourPersistenceUnit");
        EntityManager em = emf.createEntityManager();

        // 执行一次主查询,获取所有订单
        List<Order> orders = em.createQuery("SELECT o FROM Order o", Order.class).getResultList();

        // 遍历订单列表,访问每个订单的客户信息
        for (Order order : orders) {
            System.out.println(order.getCustomer().getName());
        }

        em.close();
        emf.close();
    }
}

在上述代码中,首先执行一次查询获取所有订单(这是 “1”),然后在遍历订单列表时,对于每个订单,当访问其关联的客户信息时,由于默认是懒加载,会触发一次额外的查询来获取客户信息(这就是 “N”)。如果有 100 个订单,就会执行 1 + 100 次查询。

3. 解决方案

3.1 使用 JOIN FETCH

在 JPQL 中使用 JOIN FETCH 关键字,将关联实体一次性查询出来,避免额外的查询。

List<Order> orders = em.createQuery("SELECT o FROM Order o JOIN FETCH o.customer", Order.class).getResultList();

这样,在一次查询中就会将订单和关联的客户信息一起查询出来,不会再触发额外的查询。

3.2 使用 @BatchSize 注解

在关联实体的字段上使用 @BatchSize 注解,JPA 会将多个关联实体的查询合并成一个批量查询。

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.BatchSize;

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @BatchSize(size = 20)
    private Customer customer;

    // getters and setters
}

这里设置 @BatchSize(size = 20) 表示每次批量查询 20 个关联实体,减少查询次数。

3.3 使用 @Fetch(FetchMode.SUBSELECT) 注解

在关联实体的字段上使用 @Fetch(FetchMode.SUBSELECT) 注解,JPA 会使用子查询的方式一次性查询出所有关联实体。

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @Fetch(FetchMode.SUBSELECT)
    private Customer customer;

    // getters and setters
}

这样也可以避免 1 + N 问题,减少查询次数。


Suggest Changes

Next Post
GitFlow 插件入门