Удаление определенного дочернего объекта при обновлении родительского объекта

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

У меня Object говорит «отдел» и у него есть список сотрудников. Создание моего отдела работает нормально, проблема, с которой я столкнулся, когда обновлял объект отдела.

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

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

Контроллер

@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 используется для обновления Department Api

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

    ]
}

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

shivam 26.05.2018 11:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
217
1

Ответы 1

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, а затем будет выполнять выборку для заполнения коллекции. При выборе 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)

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

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

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

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

ОБНОВИТЬ

Проблема с вашим обновлением 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-компонента и напрямую вызвать соответствующий метод репозитория сотрудника.

я постараюсь сообщить вам

shivam 26.05.2018 18:11

Я попробовал получить тип нетерпения, теперь Api работает нормально, как и ожидалось, но все еще та же проблема в обновлении.

shivam 26.05.2018 18:45

Еще одна вещь, я не думаю, что ленивая инициализация - это проблема, на самом деле это особенность. Я считаю, что некоторая конфигурация неверна из-за того, что возникают такие проблемы

shivam 26.05.2018 18:46

Я использую перечисление только как образец кода, я использовал строку

shivam 26.05.2018 18:46

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

shivam 26.05.2018 18:47

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

Naros 26.05.2018 19:59

#Naros, пожалуйста, найдите мой json, который я использовал для api отдела обновлений, в котором я не трогаю сотрудников, просто обновляя отдел и в конце ожидая полного объекта отдела.

shivam 27.05.2018 06:53

Я ожидал, что если ваш вызов контроллера обновления выполняется с двумя сеансами (один для обновления и один для получения), он должен работать. Можете ли вы опубликовать полученную трассировку стека исключения?

Naros 27.05.2018 07:15

У меня нет исключений. Кажется, вы не поняли, в чем проблема. Я создал отдел с 3 сотрудниками, и после этого я обновляю свой отдел (каскадный тип удаляется), не обновляя ни одного сотрудника, тогда я получаю последний обновленный отдел (который не соединяет ни одного сотрудника, но они существуют в db.)

shivam 27.05.2018 07:36

Другие вопросы по теме