java - 更新父对象时删除特定子对象

标签 java hibernate spring-boot jpa spring-data

我正在研究 Spring boot 和 spring jparepository。

我让对象说“部门”,并且它有一个员工列表。 我的部门创建工作正常,我在更新部门对象时遇到问题。

从Rest Api我得到了Department对象,其中包含需要在数据库中更新/删除/添加的员工列表,我的员工实体有一个 transient 属性说操作,基于此我正在过滤掉需要哪些操作对员工更新/删除/添加执行。

部门实体-

@Entity
@Table(name="department")
public class  Department{

    @Id @Column(name="dept_id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    public Integer id;

    @Column(name="dept_name")
    public String name;

    @Column(name="dept_code")
    public String code;

    @OneToMany(cascade = {CascadeType.REMOVE,CascadeType.REFRESH},orphanRemoval = true, 
    fetch = FetchType.LAZY,mappedBy="department")
    public List<Employee> employees;
}

员工实体 -

@Entity
@Table(name="employee")
public class Employee {

    @Id     @Column(name="emp_id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    public Integer id;

    @Column(name="emp_name")
    public String name;

    @ManyToOne
    @JoinColumn(name = "dept_id")
    public Department  department;

    @Transient
    public String operation;
}

部门服务层 -

@Transactional
@Service
public class DepartmentService {

    public Department createDepartment(Department department) {

        Department dept = departmentRepository.save(department);
        for (Employee emp : department.getEmployees()) {
            emp.setDepartment(department);
            employeeRepository.save(emp);
        }
        return dept;
    }

    public Department updateDepartment(Department department) {

        Department dept = departmentRepository.save(department);
        if (!dept.getEmployees().isEmpty()) {
            for (Employee emp : department.getEmployees()) {
                if (emp.getOperation().equalsIgnoreCase("delete")) 
                    employeeRepository.deleteById(emp.getId());
                 else 
                    employeeRepository.save(emp);
            }
        }
        return dept;
    }

    public Department getDepartment(int id) {
        return departmentRepository.getOne(id);
    }
}

调试后我得到的是 -

  1. 当我触发获取部门的 Api 时,我的服务层用 @Transactional 注释。它正在返回部门的代理对象,并且在服务层中未获取员工列表。当模型映射器将 dept 对象转换为 deptBean 时,它正在获取员工列表。为什么我能够从事务外部的代理对象获取员工对象列表。

  2. 在服务层的更新函数中,我也返回代理对象,但它也无法获取服务层中的员工列表。此外,我什至尝试在部门更新 Controller 之后调用服务层的 get 函数这也无法获取员工列表。

Controller

@RestController
public class DepartmentController {

    @Autowired
    DepartmentService departmentService;

     private ModelMapper modelMapper = new ModelMapper();

    @RequestMapping(value = "/dept", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<DepartmentBean> updateDepartment(@RequestBody DepartmentBean deptBean) {

        Department dept = modelMapper.map(deptBean, Department.class);
        Department persistedDept = departmentService.updateDepartment(dept);
        Department d  = departmentService.getDepartment(persistedDept.getId());
        DepartmentBean userDTO = modelMapper.map(d, DepartmentBean.class);
        return  new ResponseEntity<DepartmentBean>(userDTO, HttpStatus.OK);
    }

    @RequestMapping(value = "/dept/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<DepartmentBean> getDepartment(@PathVariable int id) {

        Department dept = departmentService.getDepartment(id);
        DepartmentBean userDTO = modelMapper.map(dept, DepartmentBean.class);
        return  new ResponseEntity<DepartmentBean>(userDTO, HttpStatus.OK);
    }
}

用于更新部门 Api 的 Json

{
    "id": 5,
    "name": "DEPT061",
    "code": "CODE061",
    "employees": [

    ]
}

最佳答案

My Service layer is annotated with @Transactional, when i am trigerring get Api for dept. it is returning proxy object of department and in service layer list of employees is not getting fetched . when model mapper is converting dept object to deptBean at that time it is fetching list of employees. Why I am able to fetch list of employee object from proxy object outside transaction.

您有几种方法可以解决这个问题。

我强烈不建议但可用的一种方法是将集合标记为急切地加载到映射模型中,如下所示

// inside your Department entity
@OneToMany(mappedBy = "department", fetch = FetchType.EAGER)
private List<Employee> employees;

这种方法的问题在于,虽然它可以工作,但它引入了我们所说的 SELECT N+1 ,基本上持久性提供程序将获取 Department 并遵循-up 通过发出选择来填充集合。当选择 1 个部门时,这显然不是什么大问题。当您像这样选择多个部门时,这将成为一个主要的性能问题

SELECT * FROM Department // returns 3 rows
SELECT * FROM Employees WHERE departmentId = :row1DepartmentId   
SELECT * FROM Employees WHERE departmentId = :row2DepartmentId
SELECT * FROM Employees WHERE departmentId = :row3DepartmentId

对于大型结果集,这可能会严重影响性能。

根据查询调用者的要求在查询时耦合关联提取的最佳和建议方法。换句话说,不要使用#findOne(),而是编写一个专门的查询来返回代码所需的内容。

@Query( "SELECT d FROM Department d JOIN FETCH employees WHERE d.id = :id" )
public List<Department> getDepartmentWithEmployees(Integer id)

这将避免延迟初始化问题,因为您明确要求提供商在您离开事务边界之前预先提供所需的所有信息。

In Update function of Service layer also , I am returning proxy object but that is not able to fetch list of employees in Service layer too.Also , I tried calling get function of service layer in after department updation controller even that is also not able to fetch list of employees.

因为我们通过调用 #getDepartment 解决了延迟初始化问题,所以这应该不再是问题。

From Rest Api I am getting Department object which contains the list of employees that need to be updated/deleted/added in database, My Employee Entity is having a transient property say operation on basis of that i am filtering out which operation need to be perform on employee update / delete / add.

这里有几个昵称。

我首先会考虑将 JSON 对象和数据库实体对象解耦。您实际上使用 transient 字段来污染数据库模型,以便可以将一些数据从 Controller 传递到持久层。这对我来说感觉不对。

如果您不想分离 json 对象和实体模型,那么至少将该 transient 数据放置在单独的上下文对象中,您单独填充该对象并提供给更新过程

public class EmployeeOperationContext {
  private Integer employeeId;
  private EmployeeOperation operation;
}

public enum EmployeeOperation {
  INSERT,
  UPDATE,
  DELETE
}

public void updateDepartment(
     Department dept, 
     List<EmployeeOperationContext> contexts) { 
  ...
}

这里要讲的关键点是,您可能随时需要重构数据库模型,以实现更好的性能,或者以更标准化的方式提供更好的数据库 View 。这样做并不意味着您的 REST API 将发生变化。

相反,如果 REST API 的使用者指示更改,但您不希望这些更改影响您的数据库模型,则可能会发生同样的情况。

Controller 和服务层的全部目的是弥合这些差距,而不是成为直通帮助者。因此,请按预期使用它们。您可能会认为这是很大的开销,但它肯定会改进您的设计并减少变化对频谱两端产生更大链式 react 的影响。

更新

Department 更新的问题在于,您盲目地从传入的其余调用中获取数据并将其推送到数据库,而不将其与现有数据合并。换句话说,看看这个

// Here you covert your DepartmentBean JSON object to a Department entity
Department dept = modelMapper.map( deptBean, Department.class );
// Here you overwrite the existing Department with JSON data
Department persistedDept = departmentRepository.save( dept );
// ^ this department no longer has employees because of this

有多种方法可以解决这个问题,但它们都涉及相同的前提。这里主要关注的是,您必须首先从数据库中获取现有的部门对象,以便拥有正确的状态,然后应用传入的更改。简而言之:

// Fetch department from the database
Department department = departmentRepository.get( departmentId );
// overlay the DepartmentBean data on the Department
modelMapper.map( deptBean, department, Department.class );
// save department
departmentRepository.save( department );

我最终要做的是修改服务方法以将 DepartmentBean 作为输入并在服务内执行上述操作:

@Transactional
public void updateDepartment(DepartmentBean jsonModel) {
  // Now we can read the department & apply a read lock in the trx
  Department department = repository.getWithLock( departmentId );

  // Overlay the json data on the entity instance
  modelMapper.map( jsonModel, department, Department.class );

  // save the changes
  repository.save( department );
}

您还可以在此处添加原始更新中的其他服务逻辑,以根据需要处理员工的删除。美妙之处在于,由于这一切都包含在事务绑定(bind)的服务方法中,因此您不再需要员工实体中的 transient 字段。您可以简单地从传入的 bean 参数中读取操作并直接调用适当的员工存储库方法。

关于java - 更新父对象时删除特定子对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50541140/

相关文章:

java - Spring Data JPA 在两个查询中加载一对一关系

java - 可以在提交/回滚时调用 hibernate 中开始事务

java - 使用 Prometheus 监控 Spring Boot 应用程序

spring - 考虑在你的配置中定义一个 'java.lang.String' 类型的 bean

spring - 在 Spring Boot 应用程序的测试类中禁用 Spring Cloud Config 的自动配置

spring - 无法提交 hibernate 事务;嵌套的异常是org.hibernate.TransactionException:事务未成功启动]

java - Spring MVC请求映射冲突

java - 应用 TranslateAnimation 时 ImageView 消失

java - Android 适配器上下文

java - 自动检测 Jackson for Spring