One way of modelling child parent relationships in a SQL database is to store it in the same table with a parent_id column which refers to a primary key of the same table. In this example we will be looking at how to model such a relationship in JPA. We will also build a rest api on top of it and represent it as a JSON so that you can view it as a tree structure.

Let’s get started

  1. The db model:

As you can see its pretty straightforward. We will be creating a family tree. We have a table which has three columns: ‘id’ represents the primary key ‘full_name’ holds the name of the person and a parent column which maps to the primary key of the same table. You can add more columns to it but I have used only the full_name column to keep it simple.

2. Creating the Spring Boot project

For the creation I will be using the Spring Initializer: link

I have used the following dependencies:

  • Spring Web — To build the rest api
  • H2 Database — H2 is an embedded data and I will be using this because this is an example and I don’t want to take the hassle of connecting and managing an external database plus it is very well supported by spring and spring boot’s auto configuration will do most all of the stuff and we can concentrate on our JPA implementation.
  • Spring Data JPA- Because well ‘duh’!
  • Lombok to reduce boiler plate code like manually generating getters and setters.

3. Creating the entity

The entire code can be found here: FULL CODE LINK

/**
 * @author MalkeithSingh on 11-09-2019
 */
@Entity
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({“hibernate_lazy_initializer”, “handler”})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {

    @Id
    @Getter
    @Setter
    @EqualsAndHashCode.Include
    private Long id;

    @Getter
    @Setter
    private String fullName;

    @ManyToOne(fetch = FetchType.LAZY)
    @Getter
    @Setter
    private Person parent;

    @OneToMany(fetch = FetchType.LAZY,mappedBy = “parent”)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Setter
    private Set<Person> children;

    @JsonIgnore
    public Set<Person> getChildren() {
        return children;
    }
}

There are three thing in particular to note here:

  • The ManyToOne annotation is on the parent column of the person table. This tell JPA that many rows of this table can be mapped to one row of the same table. The id of which is stored in the parent column. This gives us the parent row for a particular row.
  • The OneToMany relationship is mapped by the parent. It is just the inverse of the above line. The id of one row of this table can be there in the parent column of many rows of the table. The annotation has a mappedBy attribute which points to the parent column. This tells JPA to collect all the rows with the same parent id and put it into a collection which is a set in our case. This gives us all the children for a particular row.
  • Also I have used JsonIgnore on the get method of the children field. This is done because the children maps back to the parent so there is a loop in the relationship and during jackson deserialization this will cause infinite recursion and result in a stack overflow exception.

4. The PersonDto

/**
 * @author MalkeithSingh on 11-09-2019
 */
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonIgnoreProperties({“hibernate_lazy_initializer”, “handler”})
public class PersonDTO {

    private Long id;
    private String fullName;
    private Person parent;
    private Set<Person> children;
}

This is the data transfer object(DTO). I will deserialize this and not the entity to a JSON. Now you must be thinking that why do I need a dto when I already have a entity representing our data structure, well there are two reasons why:

  • I had added the JsonIgnore annotation so I won’t get the children attribute in the JSON. I have to map it to the different object and deserialize that. I will do this in the controller
  • Also its always a good practice to separate the DAO from the view so we don’t expose it to the consumer. And if tomorrow the consumer wants a additional field in the response that’s computed someplace else I don’t have to modify our entity.

5. The controller

/**
 * @author MalkeithSingh on 11-09-2019
 */
@RestController
@RequestMapping(“/api/v1/person”)
public class PersonController {

    @Autowired
    private PersonRepo personRepo;


    @GetMapping(“/{id}”)
    public ResponseEntity<PersonDTO> getAllDetails(@PathVariable(“id”) Long id) {
        return personRepo.findById(id).map(mapToPersonDTO).map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @GetMapping(“/{id}/siblings”)
    public ResponseEntity<Set<PersonDTO>> getAllSiblings(@PathVariable(“id”) Long id) {
        return personRepo.findById(id).map(findSiblings).map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    private Function<Person, Set<PersonDTO>> findSiblings = person -> person.getParent().getChildren().stream()
            .map(p -> PersonDTO.builder().id(p.getId()).fullName(p.getFullName()).build()).collect(Collectors.toSet());

    private Function<Person, PersonDTO> mapToPersonDTO = p -> PersonDTO.builder().id(p.getId()).fullName(p.getFullName()).parent(p.getParent()).children(p.getChildren()).build();
}

There are two endpoints in the controller.

  • The get endpoint takes in a id and returns a JSON representing the tree structure.

Url: https://localhost:8080/api/v1/person/2

Response: Returns the JSON for person with id 2 (Henry)

As you can see we have a tree structure with every person object containing both the its parent and its children.

  • The second endpoint returns all the siblings for a particular person

Url: https://localhost:8080/api/v1/person/2/siblings

Returns: sibling for id 2 i.e Henry

Response: Henry has only one sibling May

This bring us to the end of this article. Hope you enjoyed reading it !!

Feel free to visit the repo and have a look at the code. Read the readme.md file for info regarding starting up the application. Any inputs are always welcome. And if you have any doubts please leave a comment and I will get back to you.

Link for the repo: FULL CODE LINK


Malkeith Singh

Malkeith is a Web developer and an aspiring Software Architect. He loves to work on all aspects of web development, right from designing the database architecture and writing rest services to creating intuitive and beautiful user interfaces. He is very curious by nature and loves experimenting with new technologies. Apart from web development, his interests include music, travel and astrophysics.

Get in Touch

 1551 McCarthy Blvd, #207, Milpitas, CA 95035

 [email protected]

 +1 408 708 9307

Send us a Message

Maximum 200 characters allowed
I understand and agree that the information submitted in this form will be transmitted to, stored and processed by Enquero in accordance with our privacy policy
Yes I would like to receive Enquero marketing communication