I have a parent child relationship.
The parent class has the following fields (id, name) and the child class has the following columns (id, name, date, parent_id). The end resulting JSON I want to return is the following. I also always want to return the parent even if it has no children, which is why I LEFT OUTER JOIN on the child with the ON clause there
[
{
"name": "parnet1",
"child": {
"2021-01-01": {
"name": "child1"
},
"2021-01-02": {
"name": "child2"
}
}
},
{
"name": "parnet2",
"child": {}
}
}
]
Example of what my DB looks like for this sample
parents
id | name
1 | parent 1
2 | parent 2
child
id | name | date | parent_id
1 | child1 | 2021-01-01| 1
2 | child2 | 2021-01-02| 1
3 | child3 | 2020-12-31| 2
For the following example I am passing the dat of 2021-01-01
So the month is January (1) and the year is 2021
In the parent class I have a map of this to reference the child
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JsonManagedReference
@MapKey(name = "date")
private Map<LocalDate, Child> children;
And here is the query I have
@Query("select p from Parent p left join p.child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth
The problem with this is there is a 2nd query run after automatically by hibernate which is the following:
select * from Child where c.parent_id = ?
And this end up getting all the children where the join condition is not true.
So then I tried this
@Query("select new Parent(p.id, p.name, c.id, c.date, c.name) from Parent p left join p.child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth
And made this constructor
public Parent(int id, String name, int childId, LocalDate date, String childName) {
this.id = id;
this.name = name;
this.children = new HashMap<LocalDate, Child>();
if (childId != null) {
Child child = new Child();
child.setId(id);
child.setName(name);
this.children.put(date, child);
}
}
But the problem with this is I get a JSON array of length 4 when I want the top level of the array to be length 2, as there are only 2 parents in the DB currently.
How can I modify this to get the JSON payload that I want, which is posted at the top of the question.
Thank you very much
EDIT
Using the following doesn't work as child2 is not returned, if I were to pass in the date = '2021-01-01'
select distinct p from Parent p
left join fetch p.children c
where (YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMont)
or c.parent is null
EDIT 2 (about the auto generated query)
I am running this as an API with spring boot, but there is no extra processing, here is my API and current query.
@GetMapping(path = "/parents/{date}", produces = { "application/json" })
@Operation(summary = "Get all parents for date")
public @ResponseBody List<Parent> getParentsByDate(@PathVariable("date") String dateString) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate dateOccurred = LocalDate.parse(dateString, formatter);
return parentRepository.getParentsForDate(dateOccurred);
}
And then in my repository I just have my @Query and here is what I currently have in the ParentRepository class
public interface ParentRepository extends CrudRepository<Parent, Integer> {
@Query("select p from Parent p left join fetch p.children c where (YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth) or c.parent is null")
public List<Parent> getParentsForDate(@Param("dateOccurred") LocalDate dateOccurred
}
But as said the problem with this is the null check does work correctly, if the parent has any children in another month, but not the current one the parent is not returned