Spring 具有双向关系的数据 REST 投影导致 JSON 无限递归

Spring Data REST Projection with bidirectional relationship resulting JSON infinite recursion

我在一个使用 Spring Boot 2.0、Hibernate 和 Spring Data REST 的项目中工作。带有 React 的前端。
我的情况是用户可以与多家公司相关联(他拥有多家公司)。
当我尝试使用 UserRepository 或 CompanyRepository 获取某些实体时,出现错误:无法写入 JSON:无限递归 (WhosebugError);嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException: 无限递归 (WhosebugError)。

我必须使用投影来限制进入前端的数据,因为我需要实体的链接,由投影自动生成。

关注实体:

@Entity
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_user")
    protected Long id;

    @OneToMany(cascade= { CascadeType.MERGE }, fetch = FetchType.EAGER, mappedBy="user")
    private List<Company> companyList;

    // Other data
    // Getters and Setters
}

@Entity
public class Company extends CadastroEmpresaUnica {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_company")
    protected Long id;

    @ManyToOne(cascade= { CascadeType.MERGE })
    @JoinColumn(name="id_user", nullable = false)
    private User user;

    // Other data
    // Getters and Setters
}

预测:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {

    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}

我们正在使用的存储库之一:

@RepositoryRestResource(collectionResourceRel = "company", path = "companies", excerptProjection = CompanyProjection.class)
public interface CompanyRepository extends PagingAndSortingRepository<Company, Long>, CompanyRepositoryCustom, JpaSpecificationExecutor<Company> {}

搜索双向无限递归我发现了关于“@JsonManagedReference”和“@JsonBackReference”的内容,它们总是直接在实体中使用。所以我尝试在我的投影中使用它并且它起作用了。所以它解决了我的无限递归问题,但它产生了另一个问题,我无法从我的公司访问我的用户(因为显然 '@JsonBackReference' 没有让它停止递归)。
这是我对此解决方案的预测:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    @JsonManagedReference
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    @JsonBackReference
    UserProjection getUser();

    // Other Getters
}

再搜索一下,我读到了关于“@JsonIdentityInfo”的信息,它再次被用在实体中。所以我试图删除其他 Json 注释并在我的投影中使用 '@JsonIdentityInfo'。如以下例子:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    Long getId();
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    Long getId();
    UserProjection getUser();

    // Other Getters
}

没用。现在 Json 无限递归再次发生。
我是 Spring Data REST 的新手,我真的想通过阅读 Spring 文档和 Whosebug 主题来更好地理解 Spring Data Rest 的投影。我想知道我做错了什么,当然,如果我以错误的方式使用投影,但我需要继续这个项目。

首先是 SDR 双向关系 ))

要解决这种情况,请尝试向公司实体中的用户 getter 添加以下内容:

@RestResource(exported = false)
@JsonIgnore
public User getUser() {...}

你也可以尝试使用注解

此外,我认为如果您只需要获取用户的公司(以及公司的用户),则不需要使用投影。 SDR 中的关联资源是这样导出的(在您的情况下):

/users/{id}/companies
/companies/{id}/users

有点难看,但简单的解决方案可能是多一个投影(例如 CompanyWithoutUserProjection),它可以停止您的递归。

CompanyProjection {
  UserProjection getUser();
  //other getters
}


UserProjection {
  List<CompanyWithoutUserProjection> getCompanyList();
  //other getters
}


CompanyWithoutUserProjection {
  //other getters
}

我们找到的最好方法是使用 Jackson 注释 @JsonIgnoreProperties,应该在父列表中使用它来忽略子项中的自己。但是,经过几次尝试后,似乎此注释在投影中不起作用,特别是对于 Spring Data REST。
遵循正确方法的示例:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    @JsonIgnoreProperties({"user"})
    List<CompanyProjection> getCompanyList();

    // Other Getters
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}

我们针对此 Spring 数据 REST 问题发送了一个 ticket,它已被接受。我们相信在不久的将来它会得到纠正,我们可以使用它。
现在,我们调整投影,使列表对象可以使用原始投影的 "derivation",忽略导致无限递归的 属性。
跟例:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
    List<CompanyProjectionWithoutUser> getCompanyList();

    // Other Getters

    // Projection without the User, that couses infinite recursion
    public interface CompanyProjectionWithoutUser extends CompanyProjection {
        @Override
        @JsonIgnore
        UserProjection getUser();
    }
}

@Projection(name = "companyProjection", types = { Company.class }) 
public interface CompanyProjection {
    UserProjection getUser();

    // Other Getters
}