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

不使用@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:46:01