Option 1
Think in the other way, how about search on Child and get their Parent.
Add new constructors into the Parent class
public Parent(Integer id, String name, Child child) {
this.id = id;
this.name = name;
this.childs = new HashSet<>();
this.childs.add(child);
}
public Parent() {
}
Change JPA query with below script.
@Query("select new Parent(c.parent.id, c.parent.name, c) from Child c where c.name = 'child1'")
List<Parent> getParent();
Hibernate generates 3 SQLs for two Child record (More one query of all Child count)
Hibernate: select child0_.parent_id as col_0_0_, parent1_.name as col_1_0_, child0_.id as col_2_0_ from childs child0_ cross join parents parent1_ where child0_.parent_id=parent1_.id and child0_.name='child1'
Hibernate: select child0_.id as id1_0_0_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_ from childs child0_ where child0_.id=?
Hibernate: select child0_.id as id1_0_0_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_ from childs child0_ where child0_.id=?
If you execute only one SQL you can upgrade Parent class and Jpa query below.
Parent constructor:
public Parent(Integer id, String name, Integer childId, String childName) {
this.id = id;
this.name = name;
this.childs = new HashSet<>();
Child child = new Child();
child.setId(childId);
child.setName(childName);
this.childs.add(child);
}
Added new arguments into the jpa query
@Query("select new Parent(c.parent.id, c.parent.name, c.id, c.name) from Child c where c.name = 'child1'")
List<Parent> getParent();
Hibernate generates a single query.
Hibernate: select child0_.parent_id as col_0_0_, parent1_.name as col_1_0_, child0_.id as col_2_0_, child0_.name as col_3_0_ from childs child0_ cross join parents parent1_ where child0_.parent_id=parent1_.id and child0_.name='child1'
Test
@SpringBootTest
class DemoApplicationTests {
@Autowired
private ParentRepository parentRepository;
@Test
void contextLoads() {
Parent parent = new Parent();
parent.setName("p1");
parent.setChilds(new HashSet<>());
parent.getChilds().add(new Child("child1", parent));
parent.getChilds().add(new Child("child2", parent));
parentRepository.save(parent);
parent = new Parent();
parent.setName("p2");
parent.setChilds(new HashSet<>());
parent.getChilds().add(new Child("child1", parent));
parentRepository.save(parent);
List<Parent> parents = parentRepository.getParent();
Assertions.assertEquals(2, parents.size());
for (Parent parent1 : parents) {
System.out.println(parent1);
}
}
}
Output
Parent(id=1, name=p1, childs=[Child(id=3, name=child1)])
Parent(id=4, name=p2, childs=[Child(id=5, name=child1)])
Option 2
Fetch all children and manage Parent on java side.
public interface ChildRepository extends JpaRepository<Child, Integer> {
@Query("select c from Child c left join fetch c.parent where c.name = 'child1'")
List<Child> search();
}
Hibernate generates single SQL.
Hibernate: select child0_.id as id1_0_0_, parent1_.id as id1_1_1_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_, parent1_.name as name2_1_1_ from childs child0_ left outer join parents parent1_ on child0_.parent_id=parent1_.id where child0_.name='child1'
Test
@SpringBootTest
class DemoApplicationTests2 {
@Autowired
private ParentRepository parentRepository;
@Autowired
private ChildRepository childRepository;
@Test
void contextLoads() {
Parent parent = new Parent();
parent.setName("p1");
parent.setChilds(new HashSet<>());
parent.getChilds().add(new Child("child1", parent));
parent.getChilds().add(new Child("child2", parent));
parentRepository.save(parent);
parent = new Parent();
parent.setName("p2");
parent.setChilds(new HashSet<>());
parent.getChilds().add(new Child("child1", parent));
parentRepository.save(parent);
// search children
List<Child> children = childRepository.search();
// create parent hash map
Map<Integer, Parent> parentMap = children.stream()
.map(Child::getParent)
.distinct()
.peek(p -> {
p.setChilds(new HashSet<>());
})
.collect(Collectors.toMap(Parent::getId, p -> p));
// add children manually
for (Child child : children) {
parentMap.get(child.getParent().getId()).getChilds().add(child);
}
// get parents
Collection<Parent> parents = parentMap.values();
Assertions.assertEquals(2, parents.size());
for (Parent parent1 : parents) {
System.out.println(parent1);
}
}
}
Output
Parent(id=1, name=p1, childs=[Child(id=3, name=child1)])
Parent(id=4, name=p2, childs=[Child(id=5, name=child1)])
Extra
Fetch all Parent and fill matched Child records. We need some changes on Parent constructor pre-defined and a new JPA query that is where is replaced with and keyword.
Parent Constructor:
There is an extra childId control to avoid inserting a null child record.
public Parent(Integer id, String name, Integer childId, String childName) {
this.id = id;
this.name = name;
this.childs = new HashSet<>();
if (childId != null) {
Child child = new Child();
child.setId(childId);
child.setName(childName);
this.childs.add(child);
}
}
ParentRepository: There is no where key, because it restricts our result, we need just and instead of it.
@Query("select new Parent(p.id, p.name, c.id, c.name) from Parent p left outer join Child c on c.parent = p and c.name = 'child2'")
List<Parent> getParent();
Hibernate Query:
There is a single query just executed.
Hibernate: select parent0_.id as col_0_0_, parent0_.name as col_1_0_, child1_.id as col_2_0_, child1_.name as col_3_0_ from parents parent0_ left outer join childs child1_ on (child1_.parent_id=parent0_.id and child1_.name='child2')
Test
List<Parent> parents = parentRepository.getParent();
Assertions.assertEquals(2, parents.size());
for (Parent parent1 : parents) {
System.out.println(parent1);
}
Output: We are searching child2 and one record has this one, other is empty.
Parent(id=1, name=p1, childs=[Child(id=2, name=child2)])
Parent(id=4, name=p2, childs=[])