You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中SQL日志出现额外查询:为何存在大量隐藏SQL请求?

问题原因分析与解决方案

这种情况是JPA默认加载策略+JSON序列化触发懒加载导致的额外SQL查询,我来一步步拆解原因并给出解决办法:

一、为什么会触发额外的Project查询?

你的DailyEntry实体里的@ManyToOne private Project project;和@ManyToOne private Employee employee;,按照JPA规范,@ManyToOne和@OneToOne关联的默认加载类型是FetchType.EAGER(立即加载)。

当你调用dailyEntryRepository.findByEmployeeId(employeeId)时,Spring Data JPA会先执行1条SQL查询所有符合条件的DailyEntry,然后为每个DailyEntry单独发送SQL去查询对应的Project和Employee——这就是经典的N+1查询问题(N是DailyEntry的数量)。

而Project里的Customer(@ManyToOne,默认EAGER)和DefaultDailyEntrySettings(@OneToOne,默认EAGER),会在查询Project的时候被一起加载,所以这部分被包含在同一条SQL里,对应你看到的第一条额外SQL。

二、为什么会触发Project关联Employee的查询?

你的Project实体里的@ManyToMany private List<Employee> employees;,默认加载类型是FetchType.LAZY(懒加载)。当你的API返回DailyEntry列表时,Jackson需要把这些对象序列化为JSON,会遍历Project的所有属性(包括employees)。

此时employees是Hibernate生成的代理集合,Jackson访问它的瞬间会触发Hibernate去数据库执行SQL,查询该Project关联的所有员工——这就是第二条额外SQL的来源。

三、解决办法

1. 优化查询,避免N+1问题

首先把DailyEntry里的关联改为懒加载,然后用Fetch Join一次性加载所有需要的关联数据:

第一步:修改DailyEntry的关联加载策略

@Data
@Entity
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DailyEntry {
    // ... 其他字段
    @ManyToOne(fetch = FetchType.LAZY)
    private Project project;
    @ManyToOne(fetch = FetchType.LAZY)
    private Employee employee;
}

第二步:在Repository中定义带Fetch Join的查询

替换原来的findByEmployeeId方法,用JPQL手动指定关联加载:

@Query("SELECT de FROM DailyEntry de JOIN FETCH de.project p JOIN FETCH p.customer JOIN FETCH p.defaultDailyEntrySettings WHERE de.employee.id = :id")
List<DailyEntry> findByEmployeeId(@Param("id") long id);

这样执行查询时,会通过一条SQL一次性加载DailyEntry、Project、Customer、DefaultDailyEntrySettings,彻底避免N+1的额外查询。

2. 阻止Jackson触发懒加载初始化

针对Project里的employees集合,你有几种选择:

  • 方式一:用@JsonIgnore忽略该属性
    如果API返回的JSON不需要employees字段,直接在属性上标记:

    @ManyToMany
    @JoinTable(
        name = "employee_projects",
        joinColumns = @JoinColumn(name = "project_id"),
        inverseJoinColumns = @JoinColumn(name = "employee_id")
    )
    @JsonIgnore
    private List<Employee> employees;
    
  • 方式二:忽略Hibernate代理属性
    在Project实体上添加@JsonIgnoreProperties,避免Jackson序列化时触发懒加载:

    @Data
    @Entity
    @ToString(exclude = {"employees"})
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    public class Project {
        // ... 其他属性
    }
    
  • 方式三:使用Jackson的Hibernate模块
    引入专门处理Hibernate懒加载的模块,它会自动识别代理对象,不会触发初始化:
    先加Maven依赖:

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

    然后配置ObjectMapper:

    @Configuration
    public class JacksonConfig {
        @Bean
        public ObjectMapper objectMapper() {
            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(new Hibernate5Module());
            return mapper;
        }
    }
    

内容的提问来源于stack exchange,提问作者M.Dietz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:01:31