JPA @OneToOne select lists with N+1 queries
Asked Answered
V

3

8

I'm actually trying to use JPA @OneToOne annotation to link a Child entity to its Parent.

It's working well, except the fact that when getting a list of Childs, the JPA engine (Hibernate in this case) make 1+n queries.

Here is the log of the Hibernate queries :

select child0_.id as id1_0_, child0_.parent as parent3_0_, child0_.value as value2_0_ from child child0_
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?
select parent0_.id as id1_1_0_, parent0_.something as somethin2_1_0_ from parent parent0_ where parent0_.id=?

Using exactly the same entities definition, when I get a child in particular, JPA executes the query with expected JOIN :

select child0_.id as id1_0_0_, child0_.parent as parent3_0_0_, child0_.value as value2_0_0_, parent1_.id as id1_1_1_, parent1_.something as somethin2_1_1_ from child child0_ left outer join parent parent1_ on child0_.parent=parent1_.id where child0_.id=?

Here is the Child entity definition :

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "child")
public class Child {

    @Id
    private Long   id;
    @Column
    private String value;
    @OneToOne(optional = false)
    @JoinColumn(name = "parent")
    private Parent parent;

}

And the Parent entity :

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "parent")
public class Parent {

    @Id
    private Long   id;
    @Column
    private String something;

}

You can find a complete example of running code here : https://github.com/Alexandre-Carbenay/demo-jpa-onetoone

Is there a way to avoid the 1+n queries when getting the list of Child entities with Parent ?

Votaw answered 11/12, 2017 at 13:56 Comment(2)
Is the parent mandatory? If yes set optional=false in the OneToOne annotation.Govan
I've tried adding this optional=false, but it does not change anythingVotaw
V
8

I finally found a better solution than JOIN FETCH that also works with QueryDsl, using @EntityGraph annotation on repository methods.

Here is the updated Child definition :

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@NamedEntityGraph(name = "Child.withParent", attributeNodes = @NamedAttributeNode("parent"))
@Table(name = "child")
public class Child {
    @Id
    private Long   id;
    @Column
    private String value;
    @OneToOne(optional = false)
    @JoinColumn(name = "parent")
    private Parent parent;
}

And the ChildJpaRepository definition :

public interface ChildJpaRepository extends JpaRepository<Child, Long>, QueryDslPredicateExecutor<Child> {

    @Override
    @EntityGraph("Child.withParent")
    List<Child> findAll();

    @Override
    @EntityGraph("Child.withParent")
    List<Child> findAll(Predicate predicate);

}

Thanks to Simon Martinelli and Vlad Mihalcea for your help

Votaw answered 13/12, 2017 at 10:46 Comment(0)
G
2

I can reproduce your observations but I see no reason why Hibernate is doing this.

A solution to avoid the queries is to use JOIN FETCH like

select c from Child c join fetch c.parent
Govan answered 12/12, 2017 at 8:17 Comment(1)
The point is that I would like not to write the query myself, as I will use pagination and filters in a more complex situation than in the example But thanks for the help, it may be the solution I'll have to use in the endVotaw
Y
1

By default, the @OneToOne and @ManyToOne associations use FetchType.EAGER, and that's why you see the N+1 query issue.

So, the solution is fairly simple, just set the fetch strategy to LAZY:

@OneToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "parent")
private Parent parent;

If you have a bidirectional @OneToOne association, the parent-side cannot be made lazy unless you use bytecode enhancement.

Yah answered 12/12, 2017 at 8:27 Comment(5)
Actually, I do not want my association to be loaded lazily, but eagerly. What I really want is JPA to use one single database query to get the children and their parent associationVotaw
The just use JOIN FETCH. However, for EAGER associations, all your queries will require JOIN FETCH, and direct fetching via entityManager.find will always trigger secondary queries. That's why EAGER is bad.Yah
I want to use EAGER because in that case, I'm sure I will need the associated type information (special query for list rendering purposes), so it makes sense to use EAGER in that situation. But I don't understand what you mean : are you saying that, when using Hibernate EntityManager, if I define JOIN FETCH in combination with EAGER, hibernate will use both the JOIN in initial query, then secondary queries for each associated type ? But if I use only JPA and don't interact with EntityManager directly, everything seems to be OK: select ... from child child0_ inner join parent parent1_ on ...Votaw
EAGER means always, not just in one case, and you already have a query, so it will work just fine with LAZY at map time + JOIN FETCH at query time. Hibernate EntityManager is just JPA, like EclipseLink or OpenJPA. if you use JPA and load the Child entity by its id (entityManager.find(Child.class, childId)), Hibernate will issue a JOIN for this association even if you don't need it. If you execute a JPQL query and forget to JOIN FETCH, a secondary query for every associated entity will be generated,Yah
OK now I understand why you recommand using FETCH JOIN instead of EAGER association : to ensure that no mistake / forgetting could imply secondary queries. Thanks for the adviceVotaw

© 2022 - 2024 — McMap. All rights reserved.