JPA 懒加载问题

文章目录

 

问题描述

因为设计树形结构的实体中用到了多对一,一对多的映射关系,在加载这个实体对象的时候,因为JPA的懒加载特效会导致触发N+1的问题,通常1的这方是通过1条SQL查找得到的1个对象或1个集合,由于关联的存在 ,又需要将这个对象(或集合)关联的集合取出,1这方的集合数量是N,则要发出N条SQL,于是本来的1条联表查询SQL可解决的问题变成了N+1条SQL。
例如以下场景,后台管理系统菜单往往都是树结构的,一般会存在多个菜单和子菜单,如下:

1.实体类

@Data
@Entity
@Table(name = "menu") 
public class Menu {
    @Id
    private Integer id;
    private String menuName;
    private Integer parentId;
    @OneToMany
    @JoinColumn(name="parentId")
    private List<Menu> childList;
}
 

2.数据访问层

public interface MenuRepository extends JpaRepository<Menu,Integer> {
    List<Menu> findAllByParentIdIsNull();
}
 

插入基础数据,以下插入了2个根菜单和3个子菜单

    @Autowired
    MenuRepository menuRepository;
    {
   			Menu menu = new Menu();
            menu.setId(1);
            menu.setMenuName("系统管理");
            Menu menu2 = new Menu();
            menu2.setId(2);
            menu2.setMenuName("用户管理");
            menu2.setParentId(1);
            Menu menu3 = new Menu();
            menu3.setId(3);
            menu3.setMenuName("角色管理");
            menu3.setParentId(1);
            Menu menu4 = new Menu();
            menu4.setId(4);
            menu4.setMenuName("报表统计");
            Menu menu5 = new Menu();
            menu5.setId(5);
            menu5.setMenuName("按月统计");
            menu5.setParentId(4);
            menuRepository.save(menu);
            menuRepository.save(menu2);
            menuRepository.save(menu3);
            menuRepository.save(menu4);
            menuRepository.save(menu5);
 }
 

3.测试触发N+1

        List<Menu> menuList = menuRepository.findAllByParentIdIsNull();
        System.out.println("一级菜单数量="+menuList.size());
        for (Menu menu : menuList) {
            System.out.println("菜单名称="+menu.getMenuName()+"的子菜单数量="+menu.getChildList().size());
        }
 

可以看到执行的sql一共打印了3条sql,第1条sql查询出所有的根菜单,第2和第3条则是根据根菜单的Id去查询对应的子菜单信息。
在这里插入图片描述

4.解决N+1的问题

在实体类加@NamedEntityGraph注解,并且通过@NamedAttributeNode注解关联上需要一起加载的模型类

@NamedEntityGraph(name = "menu.findAll", attributeNodes = {
        @NamedAttributeNode(value = "childList")
})
public class Menu {}
 

在数据访问层通过@EntityGraph的value指定需要使用@NamedEntityGraph注解里配置的name名称

 	@EntityGraph(value = "menu.findAll", type = EntityGraph.EntityGraphType.FETCH)
    List<Menu> findAllByParentIdIsNull();
 

然后再执行测试代码,可以看到下面只打印了一条sql,代表着N+1的问题消失了。
在这里插入图片描述

5.jackson序列化导致的N+1问题

标签4解决的N+1问题只是在遍历获取的元素的时候,当没有遍历直接返回数据给页面时候又会导致这个问题。
问题复现:

@RestController
public class TestController {
    @Autowired
    private MenuRepository menuRepository;
    @GetMapping("/test")
    public List<Menu> menuList(){
        List<Menu> menuList = menuRepository.findAllByParentIdIsNull();
        return menuList;
    }
}
 

在这里插入图片描述
在这里插入图片描述

解决N+1问题
添加jackson-datatype-hibernate5包

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-hibernate5</artifactId>
            <version>2.13.2</version>
        </dependency>
 

重写 SpringMvc的 MappingJackson2HttpMessageConverter,将Hibernate5Module这个Module 注册到ObjectMapper。

@Configuration
public class WebMvcConfig {
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = converter.getObjectMapper();
        Hibernate5Module hibernate5Module = new Hibernate5Module();
        mapper.registerModule(hibernate5Module);
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return converter;
    }
}
 

再次访问http://localhost:8013/test即可发现控制台只打印1条sql了。

 但是获取关联属性时,需要主动获取主键,或者映射工具,才能将关联对象取出来
 
posted @ 2023-03-03 10:42  正怒月神  阅读(144)  评论(0编辑  收藏  举报