如何在 Spring Boot 中使用 Hibernate/JPA 返回多级 json

Posted

技术标签:

【中文标题】如何在 Spring Boot 中使用 Hibernate/JPA 返回多级 json【英文标题】:How can I return multi-level json using Hibernate/JPA in Spring Boot 【发布时间】:2018-04-09 05:34:35 【问题描述】:

我有一个 Postgres 数据库,其中包含 4 个表 Parent、Children、Groups 和 Group_Membership。

组可以有多个父母,父母可以有多个组。父母可以有多个孩子,但孩子只能有一个父母。

这是模式的简化版本。

我正在使用带有 Hibernate JPA 的 Spring Boot。

Parent.java

@Entity
@Table(name = "parents")
public class Parent 
    @Id
    @GeneratedValue
    @Column(name="parent_id")
    private Long parentId;
    @Column(name= "first_name")
    private String firstName;
    @Column(name= "last_name")
    private String lastName;
    @OneToMany(mappedBy="parent")
    private Set<Child> children;
    @ManyToMany(cascade =  CascadeType.ALL )
    @JoinTable(
            name= "Group_Membership",
            joinColumns =  @JoinColumn(name = "parent_id") ,
            inverseJoinColumns =  @JoinColumn(name = "group_id") 
    )
    private Set<Group> groups = new HashSet<>();

    //Constructor 
    //Getters and Setters

Child.java

@Entity
@Table(name = "children")
public class Child 
    @Id
    @GeneratedValue
    @Column(name= "child_id")
    private Long childId;
    @Column(name= "first_name")
    private String firstName;
    @Column(name= "last_name")
    private String lastName;
    @ManyToOne
    @JoinColumn(name="parent_id", nullable=false)
    private Parent parent;

    //Constructor 
    //Getters and Setters

Group.java

@Entity
@Table(name = "groups")
public class Group 
    @Id
    @GeneratedValue
    @Column(name= "group_id")
    private Long groupId;
    private String name;
    @ManyToMany(mappedBy = "groups")
    private Set<Parent> parents = new HashSet<>();

    //Constructor 
    //Getters and Setters

我为所有这些设置了如下存储库:

public interface GroupRepository extends PagingAndSortingRepository<Group, Long> 
    @RestResource(rel = "name-contains", path = "containsName")
    Page<Group> findByNameContains(@Param("name") String name, Pageable page);

组成员表

CREATE TABLE GROUP_MEMBERSHIP (
  PARENT_ID INT NOT NULL,
  GROUP_ID INT NOT NULL,
  PRIMARY KEY (PARENT_ID, GROUP_ID),
  CONSTRAINT GROUP_MEMBERSHIP_IBFK_1
   FOREIGN KEY (PARENT_ID) REFERENCES PARENTS (PARENT_ID),
  CONSTRAINT GROUP_MEMBERSHIP_IBFK_2
   FOREIGN KEY (GROUP_ID) REFERENCES GROUPS (GROUP_ID)
);

当我去 http://localhost:8080/groups

我收到了这样的回复:


  "_embedded": 
    "groups": [
      
        "name": "Hyde Park",
        "_links": 
          "self": 
            "href": "http://localhost:8080/groups/1"
          ,
          "group": 
            "href": "http://localhost:8080/groups/1"
          ,
          "parents": 
            "href": "http://localhost:8080/groups/1/parents"
          
        
      
    ]
  ,
  "_links": 
    "self": 
      "href": "http://localhost:8080/groups"
    ,
    "profile": 
      "href": "http://localhost:8080/profile/groups"
    ,
    "search": 
      "href": "http://localhost:8080/groups/search"
    
  ,
  "page": 
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "number": 0
  

然后我想看群里的家长我去http://localhost:8080/groups/1/parents

回应


  "_embedded": 
    "parents": [
      
        "firstName": "Cherice",
        "lastName": "Giannoni",
        "_links": 
          "self": 
            "href": "http://localhost:8080/parents/1"
          ,
          "parent": 
            "href": "http://localhost:8080/parents/1"
          ,
          "groups": 
            "href": "http://localhost:8080/parents/1/groups"
          ,
          "children": 
            "href": "http://localhost:8080/parents/1/children"
          
        
      ,
      
        "firstName": "Aylmer",
        "lastName": "Feckey"
        "_links": 
          "self": 
            "href": "http://localhost:8080/parents/2"
          ,
          "parent": 
            "href": "http://localhost:8080/parents/2"
          ,
          "groups": 
            "href": "http://localhost:8080/parents/2/groups"
          ,
          "children": 
            "href": "http://localhost:8080/parents/2/children"
          
        
      
    ]
  ,
  "_links": 
    "self": 
      "href": "http://localhost:8080/groups/1/parents"
    
  

最后,当我想看到组中第一个父母的孩子时,我会去 http://localhost:8080/parents/1/children

回应


  "_embedded": 
    "children": [
      
        "firstName": "Richard",
        "lastName": "Giannoni"
        "_links": 
          "self": 
            "href": "http://localhost:8080/children/2"
          ,
          "child": 
            "href": "http://localhost:8080/children/2"
          ,
          "parent": 
            "href": "http://localhost:8080/children/2/parent"
          
        
      ,
      
        "firstName": "Deeanne",
        "lastName": "Giannoni"
        "_links": 
          "self": 
            "href": "http://localhost:8080/children/1"
          ,
          "child": 
            "href": "http://localhost:8080/children/1"
          ,
          "parent": 
            "href": "http://localhost:8080/children/1/parent"
          
        
      
    ]
  ,
  "_links": 
    "self": 
      "href": "http://localhost:8080/parents/1/children"
    
  

我希望能够调用一个端点,例如 http://localhost:8080/groups/search/findAllGroupMembers?group_id=1

并让它返回包含组、组中所有父级以及每个父级的所有子级的多级 json。

我知道如何编写带有子查询的查询来返回此信息,但我只是好奇是否有更“JPA/Hibernate”的方式来执行此操作?

谢谢!

编辑:使用 Alan Hay 的答案修复

GroupFullProjection.java

@Projection(name = "groupFullProjection", types = Group.class)
public interface GroupFullProjection 
    Long getGroupId();
    String getName();
    Set<ParentFullProjection> getParents();

ParentFullProjection.java

@Projection(name = "parentFullProjection", types = Parent.class)
public interface ParentFullProjection 
    Long getParentId();
    String getFirstName();
    String getLastName();
    Set<Child> getChildren();

包含所有必需信息的 json 响应

端点:http://localhost:8080/groups/1?projection=groupFullProjection


  "name": "Hyde Park",
  "groupId": 1,
  "parents": [
    
      "children": [
        
          "firstName": "Richard",
          "lastName": "Giannoni",
        ,
        
          "firstName": "Deeanne",
          "lastName": "Giannoni",
        
      ],
      "parentId": 1,
      "firstName": "Cherice",
      "lastName": "Giannoni",
      "_links": 
        "self": 
          "href": "http://localhost:8080/parents/1?projection",
          "templated": true
        ,
        "groups": 
          "href": "http://localhost:8080/parents/1/groups"
        ,
        "children": 
          "href": "http://localhost:8080/parents/1/children"
        
      
    ,
    
      "children": [
        
          "firstName": "Hanson",
          "lastName": "Feckey",
        
      ],
      "parentId": 2,
      "firstName": "Aylmer",
      "lastName": "Feckey",
      "_links": 
        "self": 
          "href": "http://localhost:8080/parents/2?projection",
          "templated": true
        ,
        "groups": 
          "href": "http://localhost:8080/parents/2/groups"
        ,
        "children": 
          "href": "http://localhost:8080/parents/2/children"
        
      
    
  ],
  "_links": 
    "self": 
      "href": "http://localhost:8080/groups/1"
    ,
    "group": 
      "href": "http://localhost:8080/groups/1?projection",
      "templated": true
    ,
    "parents": 
      "href": "http://localhost:8080/groups/1/parents"
    
  

【问题讨论】:

我认为问题在于父母和孩子都懒加载。尝试急切地加载它。 ***.com/questions/29602386/… 感谢您的链接。我将对 fetchTypes 和 NamedEntityGraph 注释做更多的研究。 你用什么来生成 json?你能发布负责的代码吗? Tom,我上面包含的存储库文件会生成 json。最重要的是说“扩展 PagingAndSortingRepository”的部分你也可以扩展 CrudRepository。 docs.spring.io/spring-data/commons/docs/current/api/org/… 【参考方案1】:

懒惰/急切的加载与它完全无关。您的应用程序中的 REST 服务由 Spring Data Rest 提供,了解它为何如此以及如何更改它可能值得学习。

https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.fundamentals

本质上,您可以获得关联的链接,因为这些实体有自己的存储库,这些存储库作为 REST 资源公开。如果子/父不作为 REST 资源公开,则数据将被内联(因为没有其他方法可以访问它们)。

但是,您可以使用 Projections 来获取数据的替代视图。因此,您可以定义一个内联关联的投影。客户端可以请求此特定数据视图。

例如http://localhost:8080/groups/1?projection=groupFullProjection

这涉及创建一个简单的接口,该接口定义要在该数据视图中公开的属性。

见:https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts

这可能看起来像:

@Projection(name = "parentFullProjecton", types =  Parent.class )
interface ParentFullProjection

    // inline the child collection
    Set<Child> getChildren();

    // other fields


@Projection(name = "groupFullProjection", types =  Group.class )
interface GroupFullProjection

    //inline the parents collection and use the view which inlines Children
    Set<ParentFullProjecton> getParent();

    // other fields

【讨论】:

以上是关于如何在 Spring Boot 中使用 Hibernate/JPA 返回多级 json的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot - org.springframework.beans.factory.BeanCreationException:使用名称创建 bean 时出错

springboot之jpa支持

如何在 spring-boot 中禁用 spring-data-mongodb 自动配置

如何在 Spring Boot 中使用 @Transactional 注解

如何在 Spring Boot 中使用 Spring Security 启用 CORS

如何在 Spring Boot 中使用 Spring Security 配置 CORS? [复制]