4

I have a Project entity with a oneToMany relationship with Event entity

public class Project {
   ....

   @OneToMany(mappedBy = "dossier", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
   private List<Event> events;

}

I have a ProjectService class

@Service
@Transactional
public class ProjectService {

    public List<Project> findAll() {
        return (List<Project>) projectRepository.findAll();
    }
}

And a ProjectController

@RestController
@RequestMapping(value = "/projects")
public class ProjectController {

    @RequestMapping(method= RequestMethod.GET)
    public List<Project> getAllProject() {
        return projectService.findAll();
    }
}

In my client code I saw that the events of the projects are loaded and I don't understand why.

I expected that at the end of the transaction of the method findAll in DossierService, the entities will be detached. Obviously my entities are still attached as the events are retrieved during the jackson serialization in my controller.

Olivier Boissé
  • 15,834
  • 6
  • 38
  • 56

3 Answers3

8

Project.events is by default lazy-loaded because it is a OneToMany relationship.

It does not mean that Project.events is not loaded. It means that it will be loaded as soon as Project.getEvents() is called.

This occurs at the JSON serialization (when ProjectController.getAllProject() returns its response).

In order to prevent that, there are 2 ways :

  • Either you explicitly call project.setEvents(null) (or an empty list) on every project returned by ProjectService.
  • Or you add a @JsonIgnore annotation on Project.events.

EDIT: if you are using spring-boot, an OpenEntityManagerInViewInterceptor is registered by default :

Spring web request interceptor that binds a JPA EntityManager to the thread for the entire processing of the request. Intended for the "Open EntityManager in View" pattern, i.e. to allow for lazy loading in web views despite the original transactions already being completed.

You can disable this behavior by adding this line to application.properties :

spring.jpa.open-in-view=false

With this configuration, calling the getter outside of the hibernate session will cause a LazyInitializationException.

Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
  • I have always thought when you try to access lazy-loaded entites for the first time OUTSIDE the transaction, you would get a null – tkralik Mar 10 '16 at 11:19
  • Same as @TomasKralik, why the getEvents works outside the transaction. ? – Olivier Boissé Mar 10 '16 at 12:33
  • AFAIK, it depends on your ORM. With hibernate, you will get a `org.hibernate.LazyInitializationException`. With iBatis, you might get null but I did not test. – Arnaud Denoyelle Mar 10 '16 at 14:33
  • 2
    @ArnaudDenoyelle I don't understand why I don't get a LazyInitializationException in my controller during the serialization. I expected the persistence context to be closed in my controller, obviously it's still opened after the call of the method findAll as the events collection is correctly retrieved. When does spring open and close the persistence context ? (I thought it was each time a transaction is begun and closed) – Olivier Boissé Mar 10 '16 at 20:10
  • 1
    @oliv37 If you are using spring-boot, it automatically registers an `OpenEntityManagerInViewInterceptor` which binds a JPA EntityManager to the thread for the entire processing of the request. If you want to prevent this behavior, you can add `spring.jpa.open-in-view=false` in application.properties. Then, you will get the expected `LazyInitializationException`. – Arnaud Denoyelle Mar 11 '16 at 09:14
1

There are two possibilities:

  1. An OpenSessionInView or OpenEntityManagerInView bean has been defined. This type of bean causes the Session or EntityManager, respectively, to be opened at the time the controller is invoked and remains open until the controller's body has been serialized.

    It is typically considered acceptable behavior in demo/small applications, but it's highly frowned upon in larger more complex applications as your queries should return fully initialized entities required for your view, controller, or logic.

  2. The JSON library has a hibernate addon enabled where it is capable of reattaching and hydrating the entity during the serialization process.

Otherwise, the default behavior that you expect where the Session/EntityManager has closed and the entity is detached once it's returned by the service is accurate. The expected behavior would be a LazyInitializationException otherwise.

Community
  • 1
  • 1
Naros
  • 19,928
  • 3
  • 41
  • 71
0

Your entities are still attached until:

  • You ask the entity manager to clear the persistence context using entityManager.clear()
  • You ask the entity manager to detach your entity using entityManager.detach(project) for every project.

But if you know that your events will be loaded most of the time, you should then consider using FetchType.EAGER so that everything would be fetched at once.

  • 1
    I expected spring to close the persitence context at the end of the transaction (after the call of findAll() in DossierService), I don't understand why the persistence context is still opened in my controller. When does spring open and close the persistence context ? – Olivier Boissé Mar 10 '16 at 20:17