3

I have a Postgres Database that has 4 tables Parents, Children, Groups and Group_Membership.

Groups can have multiple parents and Parents can have multiple groups. Parents can have multiple children but children can only have one parent.

This is the dumbed down version of the schema. enter image description here

I am using Spring Boot with Hibernate JPA.

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
}

I have repositories for all of them set up like this:

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

Group Membership Table

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)
);

When I go to http://localhost:8080/groups

I get this response:

{
 "_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
 }
}

Then when I want to look at the parents in the group I go to http://localhost:8080/groups/1/parents

Response

{
 "_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"
 }
 }
}

Finally when I want to see the children of the first parent in the group I go to http://localhost:8080/parents/1/children

Response

{
 "_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"
 }
 }
}

I would like to be able to call one endpoint like http://localhost:8080/groups/search/findAllGroupMembers?group_id=1

and have it return multi level json with the group, all parents in the group, and all of the children of each parent.

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?

Thanks!

EDIT: Fixed using Alan Hay's answer

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 Response with all required information

Endpoint: 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"
 }
 }
}
asked Oct 27, 2017 at 14:56
4
  • 1
    I think the issue is that parents and childs are lazyloaded. Try to load it eagerly. stackoverflow.com/questions/29602386/… Commented Oct 27, 2017 at 15:04
  • Thanks for the link. I'll do some more research on fetchTypes and the NamedEntityGraph annotation. Commented Oct 27, 2017 at 15:13
  • What did you use to generate the json? Can you post the responsible code. Commented Oct 27, 2017 at 15:54
  • Tom, the repository file that I included above generates the json. Most importantly the part that says "extends PagingAndSortingRepository" you can also extend CrudRepository. docs.spring.io/spring-data/commons/docs/current/api/org/… Commented Oct 28, 2017 at 14:20

1 Answer 1

2

Lazy/Eager loading has absolutely nothing to do with it. The REST services in your application are provided by Spring Data Rest and having an understanding of why it looks like it does and how you can change it is probably worth learning.

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

Essentially you get the links to the associations because these entities have their own repositories which are exposed as REST respources. If children/parent were not exposed as REST reources then the data would be inlined (as there would be no other way to access them).

You can however use Projections to fetch alternative views of the data. So you can then define a projection that would inline the assocations. Clients can request this specific view of the data.

e.g. http://localhost:8080/groups/1?projection=groupFullProjection

This involves creating a simple interface that defines the properties to be exposed in that view of the data.

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

This might look something like:

@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
}
answered Oct 28, 2017 at 9:33
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.