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