3

Current stack:

  • Spring Boot 1.5.1
  • Spring Data JPA 1.11.0
  • Hibernate Core 5.2.6

Let's say we have the following @Entity structure

@Entity
class Root {

    @Id
    private Long id;

    @OneToMany
    @JoinColumn(name = "root_id")
    private Set<Child> children
}

@Entity
class Child {

    @Id
    private Long id;

    @OneToMany
    @JoinColumn(name = "child_id")
    private Set<Grandchild> grandchildren;
}

@Entity
class Grandchild {

    @Id
    private Long id;
}

When I query for all/specific Root objects Hibernate selects only from the corresponding table and the resulting objects' children Set is null a Hibernate proxy - as it should be.

When I call getChildren() Hibernate correctly initializes the collection but also (unwarrantedly) fetches each Child object's grandchildren Set.

Can someone please explain exactly why this recursive fetching is happening and is there a way to disable it?


I did some more digging and this is what I came up with: it seems to be related to the way Hibernate maps @OneToMany depending on whether the target collection is a List or Set.

private final RootRepo repo;

If the collections are Sets

public void test() {
    List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_

    all.forEach(root -> {
        System.out.println(root.getChildren() == null); // false
        System.out.println(Hibernate.isInitialized(root.getChildren())); // false

        root.getChildren().forEach(child -> {
            // SELECT child0_.* FROM children child0_
            // SELECT grandchild0_.* FROM grandchildren grandchild0_
            System.out.println(child.getGrandchildren() == null); // false
            System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // true

            child.getGrandChildren().forEach(grandchild -> {});
        });
    });
}

However, with Lists

public void test() {
    List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_

    all.forEach(root -> {
        System.out.println(root.getChildren() == null); // false
        System.out.println(Hibernate.isInitialized(root.getChildren())); // false

        root.getChildren().forEach(child -> {
            // SELECT child0_.* FROM children child0_
            System.out.println(child.getGrandchildren() == null); // false
            System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // false

            child.getGrandChildren().forEach(grandchild -> {
                // SELECT grandchild0_.* FROM grandchildren grandchild0_
            });
        });
    });
}

I am a certifiable idiot.

I'm using Lombok to generate getters/setters and the like for my POJOs and its default implementation of @EqualsAndHashCode annotation generates both methods taking into account every field.. including the subcollections.

rorschach
  • 2,871
  • 1
  • 17
  • 20

1 Answers1

0

I am quite surprised that the children of Root are actually null.

The way it works in your situation (please double check if the children are actually set as null), is that when you access the getChildren() (by invoking size() for example on it).. that collection is fetched from the database along with all its eager dependencies.

All the lazy dependencies (grandchildren in this particular case) are instantiated as Proxy objects, but there should be no sql query performed on the database for those (please check that).

Additionally

It never happened to me but just a little thing to remember.. According to the JPA, the lazy loading feature is just a hint to the persistence provider. Even when you set the fetchType as LAZY or in general you expect to have your collection dependencies lazy-loaded by default (which can done while configuring the session factory), the implementation may still decide to do an EAGER fetch:

Defines strategies for fetching data from the database. The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified.

Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
  • Indeed it isn't actually `null`, I misspoke. I've also updated my original question with some information regarding the queries and when they are executed. – rorschach Feb 08 '17 at 18:44
  • Yes.. now it makes sense.. it has been initialized for a Set because when you add to a Set, java invokes the equals and hashCode methods (its not the case for a List).. Nice case you had there.. – Maciej Kowalski Feb 08 '17 at 23:51