JPA 懒加载问题

Posted 正怒月神

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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了。

    **但是获取关联属性时,需要主动获取主键,或者映射工具,才能将关联对象取出来**

    以上是关于JPA 懒加载问题的主要内容,如果未能解决你的问题,请参考以下文章

    jpa 关于懒加载的问题

    springcloud JPA 懒加载失败

    [JavaEE - JPA] 性能优化: 4种触发懒加载的方式

    Spring Data JPA使用getOne方法报错:Method threw 'org.hibernate.LazyInitializationException' excepti

    java反射机制动态获取hibernate懒加载对象

    JPA、Spring、Hibernate 加载实体 OneToMany 关联的问题