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

