【问题标题】:How can I return multi-level json using Hibernate/JPA in Spring Boot如何在 Spring Boot 中使用 Hibernate/JPA 返回多级 json
【发布时间】: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"
    }
  }
}

【问题讨论】:

标签: java postgresql hibernate jpa spring-boot


【解决方案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
}

【讨论】:

    猜你喜欢
    • 2019-01-09
    • 1970-01-01
    • 2017-05-22
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 2020-03-16
    • 2018-07-07
    相关资源
    最近更新 更多