Удаление определенного дочернего объекта при обновлении родительского объекта - PullRequest
0 голосов
/ 26 мая 2018

Я работаю над Spring boot и spring jparepository.

У меня есть объект, скажем "отдел", и у меня есть список сотрудников.Мое создание отдела работает нормально, проблема, с которой я сталкиваюсь при обновлении объекта отдела.

Из Rest Api я получаю объект отдела, который содержит список сотрудников, которые необходимо обновить / удалить / добавить в базу данных., У моего сотрудника есть временное свойство, скажем, операция, исходя из того, что я отфильтровываю, какую операцию необходимо выполнить при обновлении / удалении / добавлении сотрудника.

Подразделение -

@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, когда я тригерринг получить Api для отдела.он возвращает прокси-объект отдела и в список сервисных слоев не попадает.когда преобразователь моделей преобразует объект dept в deptBean, в это время он выбирает список сотрудников.Почему я могу получить список объекта сотрудника из прокси-объекта вне транзакции.

  2. В функции обновления уровня сервиса также я возвращаю прокси-объект, но не могу получить списоксотрудники на уровне сервиса. Также я пытался вызвать функцию get уровня сервиса после контроллера обновления отдела, даже если он не может получить список сотрудников.

контроллер

@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 голосов
/ 26 мая 2018

Мой уровень сервиса помечен @Transactional, когда я запускаю Api для отдела.он возвращает прокси-объект отдела и в список сервисных слоев не попадает.когда преобразователь моделей преобразует объект dept в deptBean, в это время он выбирает список сотрудников.Почему я могу получить список объектов сотрудника из прокси-объекта вне транзакции.

У вас есть несколько способов справиться с этой проблемой.

Один способ, который я высоко не предлагать , но доступно, означает пометить коллекцию для активной загрузки в вашу модель отображения следующим образом

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

Проблема этого подхода заключается в том, что, хотя он будет работать, он вводит то, чтомы вызываем SELECT N+1, где в основном провайдер персистентности извлекает Department и отслеживает выбор, заполняя коллекцию.При выборе 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)

Это позволит избежать проблемы отложенной инициализации, поскольку вы явно просите поставщика предоставитьперед тем, как покинуть границу транзакции, вы получаете всю необходимую информацию.

В функции обновления уровня службы я также возвращаю прокси-объект, но не могу получить список сотрудников в службеКроме того, я попытался вызвать функцию get из сервисного уровня после контроллера обновлений отдела, даже если он не может получить список сотрудников.

Поскольку мы решили проблему отложенной инициализации с помощью вызовадо #getDepartment, это больше не должно быть проблемой.

Из Rest Api я получаю объект Department, содержащий список сотрудников, которые необходимо обновить / удалить / добавить в базу данных, My EmployeeУ объекта есть временное свойство, скажем, операция на основе того, что я отфильтровываюКакую операцию необходимо выполнить для обновления / удаления / добавления сотрудника.

Здесь есть несколько вариантов выбора.

Сначала я хотел бы рассмотреть возможность разъединения ваших объектов 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 заключается в том, что вы слепо берете данные из входящего вызова rest и отправляете их в базу данных, не объединяя их с существующими данными.Другими словами, посмотрите на это

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

. Вы можетедобавьте сюда другую служебную логику, которая была у вас в исходном обновлении, чтобы обрабатывать удаление сотрудников по мере необходимости.Прелесть состоит в том, что, поскольку все это содержится в методе службы, который связан с транзакцией, вам больше не нужно это переходное поле в сущности сотрудника.Вы можете просто прочитать операцию из входящего аргумента компонента и напрямую вызвать соответствующий метод репозитория сотрудника.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...