如何解决由于循环问题,Mapstruct 的 LazyInitializationException
我有一个开发项目,使用 Spring Data JPA 和 MapStruct 在实体和 DTO 之间进行映射。上周我决定是时候解决我推迟了一段时间的 FetchType.EAGER
与 LAZY
问题。我选择在需要时使用 @NamedEntityGraph
和 @EntityGraph
来加载属性。但是,在进行从实体到 dto 的映射时,我遇到了这个 LazyInitializationExeption
问题。我想我知道这是在哪里发生的,但我不知道如何通过它。
@NamedEntityGraph(name="Employee.full",...)
@Entity
public class Employee {
private Set<Role> roles = new HashSet<>();
}
@Entity
public class Role {
private Set<Employee> employees = new HashSet<>();
}
public interface EmployeeRepository extends JpaRepository<Employee,Long> {
@EntityGraph(value = "Employee.full")
@Override
Page<Employee> findAll(Pageable pageable);
}
@Service
public class EmployeeService {
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest); // ok
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp,new CycleAvoidMappingContext()); // this is where the exception happens
return dtos;
}
}
// also there is EmployeeDTO and RoleDTO classes mirroring the entity classes
// and there is a simple interface EmployeeMapper loaded as a spring component
// without any special mappings. However CycleAvoidingMappingContext is used.
我已经追踪到 LazyInitializationException
在映射器尝试映射角色依赖项时发生的情况。 Role
对象确实有 Set<Employee>
,因此存在循环引用。
使用 FetchType.EAGER
时 new CycleAvoidingMappingContext()
解决了这个问题,但使用 LAZY
这不再有效。
有谁知道如何避免异常并同时正确映射我的 DTO?
解决方法
问题是当代码从 findAll
返回时,实体不再被管理。因此,您有一个 LazyInitializationException
,因为您正在尝试在会话范围之外访问尚未初始化的集合。
添加eager使其工作,因为它确保集合已经被初始化。
您有两种选择:
- 使用
EAGER
提取; - 确保在您从
findAll
返回时实体仍处于管理状态。添加@Transactional
的方法应该工作:@Service public class EmployeeService { @Transactional public Page<EmployeeDTO> findAll(PageRequest pageRequest) { Page<Employee> employees = repository.findAll(pageRequest); Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp,new CycleAvoidMappingContext()); return dtos; } }
我想说的是,如果您需要初始化集合,那么急切地(使用实体图或查询)获取它是有意义的。
查看这篇文章以了解有关 entities states in Hibernate ORM 的更多详细信息。
UPDATE:发生此错误的原因似乎是 Mapstruct
正在转换集合,即使您在 DTO 中不需要它。
在这种情况下,您有不同的选择:
- 从 DTO 中删除字段
roles
。 Mapstruct 会忽略实体中的字段,因为 DTO 没有同名字段; - 为这个特定案例创建一个不同的 DTO 类,没有字段
roles
; - 使用
@Mapping
注释忽略实体中的字段:
或者,如果您有时需要@Mapping(target = "roles",ignore = true) void toDTO(...)
toDTO
方法@Mapping(target = "roles",ignore = true) void toSkipRolesDTO(...) // same signature as toDTO
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。