title: How can I return multi-level json using Hibernate/JPA in Spring Boot
body: <p>I have a Postgres Database that has 4 tables Parents, Children, Groups and Group_Membership. </p>
  
  <p>Groups can have multiple parents and Parents can have multiple groups. Parents can have multiple children but children can only have one parent. </p>
  
  <p>This is the dumbed down version of the schema. <a href="https://i.stack.imgur.com/QkRfi.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/QkRfi.png" alt="enter image description here"></a></p>
  
  <p>I am using Spring Boot with Hibernate JPA. </p>
  
  <p><strong>Parent.java</strong></p>
  
  <pre><code>@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&lt;Child&gt; children;
      @ManyToMany(cascade = { CascadeType.ALL })
      @JoinTable(
              name= "Group_Membership",
              joinColumns = { @JoinColumn(name = "parent_id") },
              inverseJoinColumns = { @JoinColumn(name = "group_id") }
      )
      private Set&lt;Group&gt; groups = new HashSet&lt;&gt;();
  
      //Constructor 
      //Getters and Setters
  }
  </code></pre>
  
  <p><strong>Child.java</strong></p>
  
  <pre><code>@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
  }
  </code></pre>
  
  <p><strong>Group.java</strong></p>
  
  <pre><code>@Entity
  @Table(name = "groups")
  public class Group {
      @Id
      @GeneratedValue
      @Column(name= "group_id")
      private Long groupId;
      private String name;
      @ManyToMany(mappedBy = "groups")
      private Set&lt;Parent&gt; parents = new HashSet&lt;&gt;();
  
      //Constructor 
      //Getters and Setters
  }
  </code></pre>
  
  <p>I have repositories for all of them set up like this: </p>
  
  <pre><code>public interface GroupRepository extends PagingAndSortingRepository&lt;Group, Long&gt; {
      @RestResource(rel = "name-contains", path = "containsName")
      Page&lt;Group&gt; findByNameContains(@Param("name") String name, Pageable page);
  }
  </code></pre>
  
  <p><strong>Group Membership Table</strong></p>
  
  <pre><code>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)
  );
  </code></pre>
  
  <p>When I go to <strong><a href="http://localhost:8080/groups" rel="nofollow noreferrer">http://localhost:8080/groups</a></strong></p>
  
  <p>I get this response: </p>
  
  <pre><code>{
    "_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
    }
  }
  </code></pre>
  
  <p>Then when I want to look at the parents in the group I go to <strong><a href="http://localhost:8080/groups/1/parents" rel="nofollow noreferrer">http://localhost:8080/groups/1/parents</a></strong></p>
  
  <p><strong>Response</strong></p>
  
  <pre><code>{
    "_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"
      }
    }
  }
  </code></pre>
  
  <p>Finally when I want to see the children of the first parent in the group I go to <strong><a href="http://localhost:8080/parents/1/children" rel="nofollow noreferrer">http://localhost:8080/parents/1/children</a></strong></p>
  
  <p><strong>Response</strong></p>
  
  <pre><code>{
    "_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"
      }
    }
  }
  </code></pre>
  
  <p>I would like to be able to call one endpoint like <strong><a href="http://localhost:8080/groups/search/findAllGroupMembers?group_id=1" rel="nofollow noreferrer">http://localhost:8080/groups/search/findAllGroupMembers?group_id=1</a></strong></p>
  
  <p>and have it return multi level json with the group, all parents in the group, and all of the children of each parent. </p>
  
  <p>I know how to write a query with sub-queries to return this information, but I was just curious if there is a more "JPA/Hibernate" way to do this?</p>
  
  <p>Thanks! </p>
  
  <p><strong>EDIT: Fixed using Alan Hay's answer</strong></p>
  
  <p><strong>GroupFullProjection.java</strong></p>
  
  <pre><code>@Projection(name = "groupFullProjection", types = {Group.class})
  public interface GroupFullProjection {
      Long getGroupId();
      String getName();
      Set&lt;ParentFullProjection&gt; getParents();
  }
  </code></pre>
  
  <p><strong>ParentFullProjection.java</strong></p>
  
  <pre><code>@Projection(name = "parentFullProjection", types = {Parent.class})
  public interface ParentFullProjection {
      Long getParentId();
      String getFirstName();
      String getLastName();
      Set&lt;Child&gt; getChildren();
  }
  </code></pre>
  
  <p><strong>json Response with all required information</strong></p>
  
  <p><strong>Endpoint: <a href="http://localhost:8080/groups/1?projection=groupFullProjection" rel="nofollow noreferrer">http://localhost:8080/groups/1?projection=groupFullProjection</a></strong></p>
  
  <pre><code>{
    "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"
      }
    }
  }
  </code></pre>