2

I am a newbie to Java Persistence API and Hibernate and using Spring JPA repositories for querying in DB. Now I have two entities in Parent <-> Child relationship with Parent entity with @OneToMany and Child entity with @ManyToOne mapping.

Parent Entity:-

@Entity
@Table(name = "PERSONS")
public class Persons {

    ...

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    public List<Cards> cards = new ArrayList<Cards>();

    ...
}

Child Entity:-

@Entity
@Table(name = "CARDS")
public class Cards {

    ...

    @ToString.Exclude
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PERSON_ID", nullable = false, insertable = false, updatable = false)
    public Person person;
    ...
}

And I am using my PersonsRepository like below :-

@Repository
public interface PersonsRepository extends JpaRepository<Persons, String> {

     ....
}

Now the fetchType being used in the relationship is LAZY at both the ends. Now whenever I tried to loop over a List and tried to process the cards for each using person.getCards(), it gives me below error:-

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.abc.Persons.cards, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
    at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
    at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

Now I have found everyone saying that using LAZY is the best approach in Hibernate and it says lot more about the correct design of code as well. I agree the way I have used person.getCards() will not have any open session and that is the reason it is giving me LazyInitializationException but the intent behind this is to save a lot more DB calls.

Assuming I have 1000 persons list, that means I have to make 1000 separate calls to getCards() for each person. That's why if I use the FETCHTYPE.EAGER in Person @OneToMany, what is the performance impact since everything will be fetched eagerly.

Need suggestions about the best practices followed for such kind of problems. TIA.

Edit:- I have a method in service class where I am using @transactional for that like below:-

@Transactional(readOnly = true)
    public void fetchData(Integer param1, Timestamp param2, Timestamp param3, List<String> param4, NavigableMap<Long, List<Cards>> param5) {
        List<Persons> validPersons = personRepo.getCardsPerPerson(param2, param3);
        if(validPersons != null && !validPersons.isEmpty()) {
            // store the cards on the basis of epoch timestamp
            prepareTimestampVsCardsMap(validPersons, param4, param5);
        }
    }

private void prepareTimestampVsCardsMap(List<Persons> validPersons, List<String> uList, NavigableMap<Long, List<Cards>> timestampVsCardsList) {
        for(Person person : validPersons) {
            Long epoch = order.getOrderTime().getTime();
            Set<Cards> cardsPerPerson = person.getCards();
}
}

Also, the query being used in repository for getting the cards associated to a person is using join fetch as below:-

@Query(value = "select p from Person p join fetch Cards c on p.id = c.id WHERE p.orderTime BETWEEN ?1 AND ?2 ORDER BY orderTime ASC")
    public List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);

I am still getting the same above mentioned LazyInitializationException. Can anyone please help.

9
  • Where exactly you are calling person.getCards()? In the Service class right? I assume you are not using "@Transactional" in your service method, in that case when you get person from repository the session is closed and your lazy initialisation if of no use. You should have "@Transactional" on your service method so that the session is closed after all your business case is done in single transaction. Commented Jun 3, 2021 at 8:54
  • It's always better to set fetching strategy to Lazy. If you need cards along with people, you should fetch it with your repository call (e.g. having @Query("select p from Person p join fetch Cards c on p.id = c.person.id") on a method, or use entity graphs). That way you have only one call to DB which fetches all the data you need. Commented Jun 3, 2021 at 9:27
  • @Lucia Please see my edit. I have added all the things and using "@Transactional "but getting the same error Commented Jun 3, 2021 at 13:56
  • @StefanGolubović I have used the query in same way using join fetch to get the list of cards associated for a person. Please see in my edit. Still no success and getting the same exception Commented Jun 3, 2021 at 13:57
  • 1
    @user2594 Sorry, I mixed SQL and JPQL. You can try something like @Query("select p from Person p join fetch p.cards where ..."). You can always find a solution for your persistence problem on vladmihalcea.com blog. Commented Jun 3, 2021 at 15:30

2 Answers 2

1

First of all, it's always better to use FetchType.LAZY instead of FetchType.EAGER. Why? Because you might not need all the data every time. If you want to return a list of Persons and display them somehow, somewhere, do you need to fetch all of their cards as well? If not, then FetchType.LAZY would be the better option, and you would then control how much data you need.

LazyInitializationException usually indicates that you didn't fetch all the data you need while your Session was opened. There are many ways to fetch associated data (none of which is keeping the Session opened while processing request):

1. using join fetch in your JPQL/HQL

@Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);

2. if you're using Spring Data, you could use @EntityGraph instead of join fetch

@EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();

That way, every time you call getPersons, it will fetch cards as well. Of course, you couldn't use this one if you have to write @Query.

If you're using Spring Data's naming conventions for some simple queries, then @EntityGraph would be an option for fetching associations.

3. using Criteria API

Again, if you're using Spring Data, this is just a fallback solution in case you end up with MultipleBagFetchException. I will not go into details for this one, but in case you encounter this exception, you'll find solution in Vlad Mihalcea's blog post The best way to fix the Hibernate MultipleBagFetchException.

Sign up to request clarification or add additional context in comments.

Comments

0

You are under the misconception that EAGER loading means Hibernate will fetch all data with one statement, this is false. With EAGER as a strategy, the framework will just do every query required to fetch all data for every entity.

Example: If one entity has 2 EAGER relationships, fetching one will result in 3 statements, one to load the entity, one for each of its relationships. If you have 3 entities, you will have 7 statements, the initial statement loading the 3 objects, plus 2 per object.

When your treatment requires everything, there is no real performance impact at the moment. But most applications are not made of one treatment. This means every treatment in your application will load everything which is EAGER, even if not needed. This will effectively slow everything down. You also risk loading all your database in memory if everything is in EAGER.

This is why LAZY is the recommended approach.

As for your LazyInitializationException, it seems in your stack trace that you are using the stream API. It's a wild guess due to missing details, but JPA/Hibernate doesn't handle sharing a session between threads, so if you are using parrallelStream it could cause the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.