不使用@PreRemove,如何实现删除实体前的关联业务逻辑处理?
问题描述
项目中有三个实体:CompanyBranch(分公司)、Department(部门)、Employee(员工),关联关系为:
CompanyBranch与Department是一对多Department与Employee是一对多
实体类代码
CompanyBranch
@Entity @Table(name = "COMPANY_BRANCH", uniqueConstraints = { @UniqueConstraint(name = "AddressUniqueConstraint", columnNames = {"COMPANY_BRANCH_CITY", "COMPANY_BRANCH_ZIP_CODE", "COMPANY_BRANCH_STREET", "COMPANY_BRANCH_COUNTRY"}) }) @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", unique = true) private String phoneNumber; }
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") 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", unique = true) private String phoneNumber; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "COMPANY_BRANCH_ID", nullable = false) private CompanyBranch companyBranch; @PrePersist @PreUpdate protected void setDefaultLastModified(){ lastModified = new Date(); } }
Employee
@Entity @Table(name = "EMPLOYEE") @Getter @Setter public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Embedded private Name name; @Pattern(regexp = "^(\\+\\d{1,3}( )?)?((\\(\\d{1,3}\\))|\\d{1,3})[- .]?\\d{3,4}[- .]?\\d{4}$", message = "Неверный формат номера!") @NotNull @NotBlank(message = "Телефон не может быть пустым!") @Column(unique = true, name = "PHONE_NUMBER") private String phoneNumber; @Embedded @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name = "HOME_CITY")), @AttributeOverride(name = "zipCode", column = @Column(name = "HOME_ZIP_CODE")), @AttributeOverride(name = "street", column = @Column(name = "HOME_STREET")), @AttributeOverride(name = "country", column = @Column(name = "HOME_COUNTRY")) }) private Address homeAddress; @Email(message = "Неверный формат почты!") @NotNull @Column(unique = true, name = "EMAIL") private String email; @Temporal(value = TemporalType.DATE) @Column(name = "EMPLOYMENT_DATE", updatable = false) @CreationTimestamp private Date employmentDate; @BatchSize(size = 4) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "EMPLOYEE_STATUS_ID") private EmployeeStatus employeeStatus; @NotNull @Convert( converter = MoneyConverter.class ) @Column(name = "SALARY", length = 63) private Money salary; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "DEPARTMENT_ID") private Department department; }
当前已实现的逻辑
删除Department时,需要将该部门下所有Employee的状态改为FIRED。因为EmployeeStatus是实体类,实体中无法注入Spring逻辑,所以放弃了@PreRemove方案,采用以下实现:
Repository层
@Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "UPDATE Employee e SET e.employeeStatus = " + "(SELECT es FROM EmployeeStatus es WHERE es.employeeStatus = 'FIRED'), " + "e.department = null " + "WHERE e.department = :department") void updateEmployeesStatusToFiredOnDelete(Department department);
Service层
@Transactional public void deleteDepartment(Department department) { if (department == null) throw new IllegalArgumentException("Illegal department for removal!"); departmentRepository.updateEmployeesStatusToFiredOnDelete(department); departmentRepository.delete(department); }
需求与疑问
现在需要实现:删除CompanyBranch时,删除其下所有Department,同时将这些部门下的Employee状态改为FIRED。不想为CompanyBranch新增类似的@Query,请问有什么其他解决方案?或者是否可以通过@PreRemove实现该需求?
解决方案
方案一:复用现有部门删除逻辑
直接复用已实现的单个部门删除逻辑,删除分公司时查询其下所有部门,循环调用删除方法:
Service层实现
@Transactional public void deleteCompanyBranch(CompanyBranch companyBranch) { if (companyBranch == null) throw new IllegalArgumentException("Illegal company branch for removal!"); // 查询分公司下所有部门 List<Department> departments = departmentRepository.findByCompanyBranch(companyBranch); // 循环删除每个部门,自动触发员工状态更新 departments.forEach(this::deleteDepartment); // 删除分公司 companyBranchRepository.delete(companyBranch); }
需要在DepartmentRepository中添加查询方法:
List<Department> findByCompanyBranch(CompanyBranch companyBranch);
该方案完全复用现有代码,无需新增SQL,逻辑清晰易维护。
方案二:JPA级联删除+实体事件监听
借助Spring Data JPA的实体事件监听实现@PreRemove逻辑,避免在实体中注入Spring Bean:
步骤1:修改CompanyBranch实体,添加级联关联
@OneToMany(mappedBy = "companyBranch", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY) private List<Department> departments;
步骤2:编写部门删除事件监听类
@Component public class DepartmentRemoveListener { private final EmployeeRepository employeeRepository; private final EntityManager entityManager; public DepartmentRemoveListener(EmployeeRepository employeeRepository, EntityManager entityManager) { this.employeeRepository = employeeRepository; this.entityManager = entityManager; } @PreRemove public void onDepartmentPreRemove(Department department) { // 查询FIRED状态的EmployeeStatus EmployeeStatus firedStatus = entityManager.createQuery( "SELECT es FROM EmployeeStatus es WHERE es.employeeStatus = 'FIRED'", EmployeeStatus.class ).getSingleResult(); // 更新该部门下员工的状态 employeeRepository.updateEmployeeStatusByDepartment(department, firedStatus); } }
步骤3:更新EmployeeRepository的批量操作方法
@Modifying(clearAutomatically = true, flushAutomatically = true) @Query("UPDATE Employee e SET e.employeeStatus = :firedStatus, e.department = null WHERE e.department = :department") void updateEmployeeStatusByDepartment(Department department, EmployeeStatus firedStatus);
步骤4:删除分公司的Service方法
@Transactional public void deleteCompanyBranch(CompanyBranch companyBranch) { if (companyBranch == null) throw new IllegalArgumentException("Illegal company branch for removal!"); companyBranchRepository.delete(companyBranch); }
删除分公司时,JPA会级联删除所有关联部门,每个部门的@PreRemove事件被触发,自动更新对应员工状态。此方案将业务逻辑转移到实体生命周期监听,符合JPA规范。
方案三:优化Repository层批量操作
新增针对分公司的员工状态批量更新方法,一次SQL完成操作:
EmployeeRepository新增方法
@Modifying(clearAutomatically = true, flushAutomatically = true) @Query("UPDATE Employee e SET e.employeeStatus = (SELECT es FROM EmployeeStatus es WHERE es.employeeStatus = 'FIRED'), e.department = null " + "WHERE e.department IN (SELECT d FROM Department d WHERE d.companyBranch = :companyBranch)") void updateEmployeesStatusToFiredByCompanyBranch(CompanyBranch companyBranch);
Service层实现
@Transactional public void deleteCompanyBranch(CompanyBranch companyBranch) { if (companyBranch == null) throw new IllegalArgumentException("Illegal company branch for removal!"); // 批量更新所有关联员工状态 employeeRepository.updateEmployeesStatusToFiredByCompanyBranch(companyBranch); // 删除分公司(需配置级联删除部门,或先删除所有部门) companyBranchRepository.delete(companyBranch); }
此方案仅需一次批量SQL,性能更优,且逻辑聚焦于员工数据操作,比为CompanyBranch单独写Query更合理。
内容的提问来源于stack exchange,提问作者denstran
相关产品推荐
相关产品推荐

