由于循环问题,Mapstruct 出现 LazyInitializationException

LazyInitializationException with Mapstruct because of cyclic issue

我有一个开发项目,使用 Spring Data JPAMapStruct 在实体和 DTO 之间进行映射。上周我决定是时候解决我推迟了一段时间的 FetchType.EAGERLAZY 问题了。我选择使用 @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 使其工作,因为它确保集合已经初始化。

你有两个选择:

  1. 使用 EAGER 获取;
  2. 当您从 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 中不需要它。 在这种情况下,您有不同的选择:

  1. 从 DTO 中删除字段 roles。 Mapstruct 将忽略实体中的字段,因为 DTO 没有同名字段;
  2. 为没有字段 roles;
  3. 的特定情况创建一个不同的 DTO class
  4. 使用@Mapping注解忽略实体中的字段:
    @Mapping(target = "roles", ignore = true)
    void toDTO(...)
    
    或者,如果您有时需要 toDTO 方法
    @Mapping(target = "roles", ignore = true)
    void toSkipRolesDTO(...) // same signature as toDTO