由于循环问题,Mapstruct 出现 LazyInitializationException
LazyInitializationException with Mapstruct because of cyclic issue
我有一个开发项目,使用 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
的代码 return 时,实体不再受管理。所以你有一个 LazyInitializationException
因为 你正试图在会话范围之外访问尚未初始化的集合.
添加 eager 使其工作,因为它确保集合已经初始化。
你有两个选择:
- 使用
EAGER
获取;
- 当您从
findAll
中 return 时,确保实体仍然受到管理。在方法中添加 @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 没有同名字段;
- 为没有字段
roles
; 的特定情况创建一个不同的 DTO class
- 使用
@Mapping
注解忽略实体中的字段:
@Mapping(target = "roles", ignore = true)
void toDTO(...)
或者,如果您有时需要 toDTO
方法
@Mapping(target = "roles", ignore = true)
void toSkipRolesDTO(...) // same signature as toDTO
我有一个开发项目,使用 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
的代码 return 时,实体不再受管理。所以你有一个 LazyInitializationException
因为 你正试图在会话范围之外访问尚未初始化的集合.
添加 eager 使其工作,因为它确保集合已经初始化。
你有两个选择:
- 使用
EAGER
获取; - 当您从
findAll
中 return 时,确保实体仍然受到管理。在方法中添加@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 没有同名字段; - 为没有字段
roles
; 的特定情况创建一个不同的 DTO class
- 使用
@Mapping
注解忽略实体中的字段:
或者,如果您有时需要@Mapping(target = "roles", ignore = true) void toDTO(...)
toDTO
方法@Mapping(target = "roles", ignore = true) void toSkipRolesDTO(...) // same signature as toDTO