上一文中我们使用@ManyToOne、@OneToMany进行自关联查询,遇到的“N+1”问题需要通过@NamedEntityGraph来解决。

Entity:

/**
 * 典型的 多层级 分类
 * <p>
 * :@NamedEntityGraph :注解在实体上 , 解决典型的N+1问题
 * name表示实体图名, 与 repository中的注解 @EntityGraph的value属性相对应,
 * attributeNodes 表示被标注要懒加载的属性节点 比如此例中 : 要懒加载的子分类集合children
 */

@Entity
@Table
@Data
@NamedEntityGraph(name = "Category.Graph", attributeNodes = {@NamedAttributeNode("children")})
public class Category {
    @Id
    @GeneratedValue
    private Long id;

    // 分类名
    private String name;

    // 一个商品分类下面可能有多个商品子分类(多级) 比如 分类 : 家用电器  (子)分类 : 电脑  (孙)子分类 : 笔记本电脑
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Category parent;                //父分类

//    @JsonInclude(JsonInclude.Include.NON_EMPTY)   // 不要使用值為null或內容為空的屬性。
    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
    private List<Category> children;       //子分类集合,用Set代替List时,请在类上添加注解@EqualsAndHashCode(exclude = "children")
}

Repository:

public interface CategoryRepository extends JpaRepository<Category, Long> {
    /**
     * 解决 懒加载 JPA 典型的 N + 1 问题
     */
    @EntityGraph(value = "Category.Graph", type = EntityGraph.EntityGraphType.FETCH)
    List<Category> findAll();

    @EntityGraph(value = "Category.Graph", type = EntityGraph.EntityGraphType.FETCH)  // 无效, 始终存在N+1问题
    Category findByName(String name);

    @EntityGraph(value = "Category.Graph", type = EntityGraph.EntityGraphType.FETCH)  // 无效, 始终存在N+1问题
    Optional<Category> findById(Long id);
}

Controller:

@RestController
public class Output {

    @Autowired
    private CategoryRepository categoryRepository;

    @GetMapping("category")
    public Category getCategory() {
        List<Category> categories = categoryRepository.findAll();
        // return categories.get(0); // 一条SQL
        // return categories.stream().filter(category -> category.getName().equals("家用电器")).findFirst().orElse(null); // 一条SQL
        return categoryRepository.findByName("家用电器"); // 无论findByName(String name)方法加不加@EntityGraph,都不会触发N+1查询,对比下文更神奇
    }

    @GetMapping("category/name/{name}")
    public Category getCategoryByName(@PathVariable String name) {
        // 虽然findByName(String name)添加了@EntityGraph,但是没有起作用, 依然存在N+1问题
        return categoryRepository.findByName(name);
    }

    @GetMapping("category/id/{id}")
    public Category getCategoryById(@PathVariable Long id) {
        // 虽然findById(Long id)添加了@EntityGraph,但是没有起作用, 依然存在N+1问题
        return categoryRepository.findById(id).orElse(null);
    }
    
    @GetMapping("categories")
    public List<Category> getCategories() {
        return categoryRepository.findAll();
    }
}

插入数据:

    @Autowired
    private CategoryRepository categoryRepository;
    
    @Test
    public void addCategory() {

        //一个 家用电器分类(顶级分类)
        Category appliance = new Category();
        appliance.setName("家用电器");
        categoryRepository.save(appliance);

        //家用电器 下面的 电脑分类(二级分类)
        Category computer = new Category();
        computer.setName("电脑");
        computer.setParent(appliance);
        categoryRepository.save(computer);

        //电脑 下面的 笔记本电脑分类(三级分类)
        Category notebook = new Category();
        notebook.setName("笔记本电脑");
        notebook.setParent(computer);
        categoryRepository.save(notebook);

        //家用电器 下面的 手机分类(二级分类)
        Category mobile = new Category();
        mobile.setName("手机");
        mobile.setParent(appliance);
        categoryRepository.save(mobile);

        //手机 下面的 智能机 / 老人机(三级分类)
        Category smartPhone = new Category();
        smartPhone.setName("智能机");
        smartPhone.setParent(mobile);
        categoryRepository.save(smartPhone);

        Category oldPhone = new Category();
        oldPhone.setName("老人机");
        oldPhone.setParent(mobile);
        categoryRepository.save(oldPhone);
    }
View Code

相关文章: