Hibernate从集合移除实体时生成额外SELECT查询的原因
我正在使用Spring Data JPA,尝试从Set集合中移除实体,但Hibernate生成了超出预期的SELECT查询。
相关代码
CompanyBranch实体
@Entity @Table(name = "COMPANY_BRANCH") @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class CompanyBranch { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull @Convert(converter = MoneyConverter.class) @Column(name = "COMPANY_BRANCH_BUDGET", length = 63) private Money budget; @Embedded @NotNull @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name = "COMPANY_BRANCH_CITY")), @AttributeOverride(name = "zipCode", column = @Column(name = "COMPANY_BRANCH_ZIP_CODE")), @AttributeOverride(name = "street", column = @Column(name = "COMPANY_BRANCH_STREET")), @AttributeOverride(name = "country", column = @Column(name = "COMPANY_BRANCH_COUNTRY")) }) private Address companyBranchAddress; @NotNull @NotBlank(message = "Номер телефона не должен быть пустым") @Pattern(regexp = "^(\\+\\d{1,3}( )?)?((\\(\\d{1,3}\\))|\\d{1,3})[- .]?\\d{3,4}[- .]?\\d{4}$", message = "Неверный формат номера!") @Column(name = "COMPANY_BRANCH_PHONE_NUMBER") private String phoneNumber; @OneToMany(mappedBy = "companyBranch", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Department> departments = new HashSet<>(); public void addDepartment(Department department) { this.departments.add(department); department.setCompanyBranch(this); } public void removeDepartment(Department department) { department.setCompanyBranch(null); departments.remove(department); } }
Department实体
@Entity @Table(name = "DEPARTMENT") @Getter @Setter public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull @NotBlank(message = "Название отдела не может быть пустым!") @Column(name = "DEPARTMENT_NAME") private String departmentName; @Temporal(TemporalType.TIMESTAMP) @Column(name = "LAST_MODIFIED", insertable = false, updatable = false) private Date lastModified; @NotNull @NotBlank(message = "Номер телефона не должен быть пустым") @Pattern(regexp = "^(\\+\\d{1,3}( )?)?((\\(\\d{1,3}\\))|\\d{1,3})[- .]?\\d{3,4}[- .]?\\d{4}$", message = "Неверный формат номера!") @Column(name = "DEPARTMENT_PHONE_NUMBER") private String phoneNumber; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "COMPANY_BRANCH_ID") private CompanyBranch companyBranch; @OneToMany(mappedBy = "department", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private Set<Employee> employees = new HashSet<>(); public void addEmployee(Employee employee) { this.employees.add(employee); employee.setDepartment(this); } public void removeEmployee(Employee employee) { this.employees.remove(employee); employee.setDepartment(null); } @PreUpdate @PrePersist protected void setDefaultLastModified(){ lastModified = new Date(); } @PreRemove protected void changeEmploysStatus() { this.employees.forEach(employee -> employee.getEmployeeStatus().setEmployeeStatus(EEmployeeStatus.FIRED)); } }
移除操作代码
CompanyBranch companyBranch = companyBranchService.getCompanyBranchById(companyBranchId); Department department = departmentService.getDepartmentById(depId); companyBranchService.removeDepartment(companyBranch, department);
移除方法
public void removeDepartment(CompanyBranch companyBranch, Department department) { companyBranch.removeDepartment(department); companyBranchRepository.saveAndFlush(companyBranch); }
Hibernate生成的查询
Hibernate: select c1_0.id, c1_0.company_branch_budget, c1_0.company_branch_city, c1_0.company_branch_country, c1_0.company_branch_street, c1_0.company_branch_zip_code, c1_0.company_branch_phone_number from company_branch c1_0 where c1_0.id=? Hibernate: select d1_0.id, d1_0.company_branch_id, d1_0.department_name, d1_0.last_modified, d1_0.department_phone_number from department d1_0 where d1_0.id=? Hibernate: select d1_0.company_branch_id, d1_0.id, d1_0.department_name, d1_0.last_modified, d1_0.department_phone_number from department d1_0 where d1_0.company_branch_id=?
疑问
请问Hibernate为何会生成最后这条SELECT查询?是否与save方法有关,或是其他原因?我猜测可能和保存操作相关,但不确定。
最后这条SELECT查询是Hibernate处理orphanRemoval=true时触发的,核心原因是集合懒加载导致的状态校验:
orphanRemoval的工作机制:当你给
@OneToMany设置orphanRemoval=true时,Hibernate需要确保从关联集合中移除的实体被彻底删除。为了做到这一点,它需要对比内存中集合的当前状态和数据库中实际存在的关联数据,找出被移除的实体。懒加载的影响:你加载
CompanyBranch时,departments集合默认是懒加载的(@OneToMany默认fetch类型为LAZY),此时内存中并没有完整的集合数据。当你修改集合并调用saveAndFlush时,Hibernate无法仅通过内存中的部分数据判断哪些实体被移除,因此会执行这条查询,拉取该CompanyBranch关联的所有Department,再和内存中的集合做比对。额外查询的触发时机:这条查询是在
saveAndFlush执行时触发的,因为Hibernate需要在刷新持久化上下文前完成集合状态的一致性校验,进而确定是否要执行删除操作。
解决建议
如果你想避免这条额外查询,可以提前初始化departments集合:
- 使用JOIN FETCH查询加载集合:修改
getCompanyBranchById方法,通过JOIN FETCH一次性加载CompanyBranch和关联的departments,这样内存中就有完整的集合数据,Hibernate无需再去数据库查询。@Query("SELECT cb FROM CompanyBranch cb JOIN FETCH cb.departments WHERE cb.id = :id") CompanyBranch getCompanyBranchByIdWithDepartments(@Param("id") Long id); - 手动初始化集合:在加载
CompanyBranch后,调用Hibernate.initialize(companyBranch.getDepartments())强制初始化懒加载集合,同样可以让Hibernate直接使用内存中的数据做比对。
另外,你的removeDepartment方法逻辑是正确的,只要集合被提前初始化,Hibernate就能直接识别出被移除的元素,不会再触发额外的SELECT查询。
内容的提问来源于stack exchange,提问作者denstran

