如何在 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 时出错
如何在 spring-boot 中禁用 spring-data-mongodb 自动配置
如何在 Spring Boot 中使用 @Transactional 注解