解决Spring Data Jpa懒加载的N+1问题

问题描述

因为设计树形结构的实体中用到了多对一,一对多的映射关系,在加载这个实体对象的时候,因为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了。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皓亮君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值