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

时间:2018-05-26 09:10:39

标签: java hibernate spring-boot jpa spring-data

我正在开发Spring boot和spring jparepository。

我有对象说“部门”,并且有一份员工名单。 我的部门创建工作正常,我正在更新部门对象时遇到问题。

来自Rest Api我正在获取Department对象,其中包含需要在数据库中更新/删除/添加的员工列表,My Employee Entity正在具有一个瞬态属性说基于我的操作我将过滤掉哪个操作需要在员工更新/删除/添加上执行。

部门实体 -

@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. 我的服务图层使用@Transactional进行注释,当我为triptring获取部门的Api时。它返回部门的代理对象,并且在服务层中没有获取员工的列表。当模型映射器在那时将dept对象转换为deptBean时,它将获取员工列表。为什么我能够从事务外的代理对象获取员工对象列表。

  2. 在Service层的Update功能中,我也返回代理对象,但是也无法获取Service层中的员工列表。而且,我尝试在部门更新控制器之后调用服务层的get功能这也无法获取员工名单。

  3. 控制器

    @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);
        }
    }
    

    Json用于更新部门Api

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

1 个答案:

答案 0 :(得分:1)

  

我的服务图层使用@Transactional进行注释,当我为triptring获取部门的Api时。它返回部门的代理对象,并且在服务层中没有获取员工的列表。当模型映射器在那时将dept对象转换为deptBean时,它将获取员工列表。为什么我能够从事务外的代理对象获取员工对象列表。

您可以通过几种方法解决此问题。

我高度不建议但可用的一种方法是将集合标记为在此映射模型中急切加载

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

这种方法的问题在于虽然它会起作用,但它引入了我们称之为SELECT N+1的内容,基本上持久性提供程序将获取Department并通过发出一个select来填充采集。选择1 Department时,这显然不是一个问题。当您选择多个这样的部门时,它将成为一个主要的性能问题

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)

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

  

在服务层的更新功能中,我也返回代理对象,但是也无法获取服务层中的员工列表。另外,我尝试在部门更新控制器之后调用服务层的获取功能,即使是无法获取员工名单。

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

  

来自Rest Api我正在获取Department对象,其中包含需要在数据库中更新/删除/添加的员工列表,My Employee Entity正在具有一个瞬态属性说基于我的操作我将过滤掉哪个操作需要在员工更新/删除/添加上执行。

这里有几个昵称。

我首先考虑将JSON对象与数据库实体对象分离。您可以使用瞬态字段有效地污染数据库模型,这样您就可以将一些数据从控制器传递到持久层。对我来说这感觉不对。

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

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

public enum EmployeeOperation {
  INSERT,
  UPDATE,
  DELETE
}

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

这里要点的关键是,在任何时候,您可能需要重构数据库模型以更好地执行或以更规范化的方式更好地查看数据库。这样做时,这并不意味着您的REST API将会发生变化。

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

控制器和服务层的重点是弥合这些差距,而不是 passthru 助手。因此,按照他们的意图使用它们。您可能会将此视为很多开销,但它肯定会改善您的设计并减少更改对频谱两端产生更大波纹影响的影响。

更新

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 );
}

您可以在此处添加原始更新中的其他服务逻辑,以根据需要处理员工的删除。美妙之处在于,由于这都包含在事务绑定的服务方法中,因此您不再需要员工实体中的瞬态字段。您只需从传入bean参数中读取操作,然后直接调用相应的员工存储库方法。