4

I'm developing a REST API with Jersey, Spring and Hibernate. I have this endpoint:

@Path("incidences")
public class IncidencesResource {

    @InjectParam
    IncidencesService incidencesService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Incidence> getIncidences(
            @QueryParam("lastupdate") String lastUpdate) {

        return incidencesService.getIncidences();

}

and the service:

@Service
public class IncidencesService {

    @Autowired
    IncidencesDAO incidencesDAO;

    @Transactional
    public List<Incidence> getIncidences() {
        List<Incidence> incidencias = incidencesDao.getIncidences();
        return incidences;
    }

}

But I get a org.hibernate.LazyInitializationException error:

Failed to lazily initialize a collection of role: com.api.blabla.Incidence.questions, no session or session was closed

questions is a @OneToMany property of Incidence.

If I put @Transactional annotation in the endpoint method, instead of in the service, it works properly. But I understand that @Transactional has to be placed at service level.

Thanks.

Héctor
  • 24,444
  • 35
  • 132
  • 243
  • Where is the exception thrown, in which class? When `Incidence` gets converted to JSON? – Predrag Maric Jul 22 '15 at 09:33
  • What if you call `Hibernate.initialize(incidences.getQuestions())` before returning `incidences` in your service method? Another solution is to fetch `questions` eagerly rather than lazily, configurable with `@OneToMany`'s `fetch` property. – sp00m Jul 22 '15 at 09:34
  • Please write spring setting xml file. – ooozguuur Jul 22 '15 at 09:34

4 Answers4

4

This does not mean @Transactional is not working. It is doing what you told it to do. The service returns an instance of Incidence without initialized collection of questions, and you are trying to access that collection outside of service's transaction (probably when Jersey tries to convert it to JSON).

In this situation, the service must return all the data needed by the caller, which means you have to initialize questions before returning the result. So, just call Hibernate.initialize(incidence.getQuestions()) in service method for every instance you return.

@Transactional
public List<Incidence> getIncidences() {
    List<Incidence> incidencias = incidencesDao.getIncidences();
    for (Incidence inc : incidencias) {
        Hibernate.initialize(inc.getQuestions());
    }
    return incidencias;
}
Predrag Maric
  • 23,938
  • 5
  • 52
  • 68
  • I'm getting the same error. What does it mean `initialize` exactly? I have debugged step by step and when I retrieve one incidence I am retrieving the nested questions, since is a one to many relationship. – Héctor Jul 22 '15 at 09:48
  • `Hibernate.initialize()` will initialize the lazy fetched objects, and `@OneToMany` iz lazy fetched by default. Are you getting exactly the same exception? Can you post the stacktrace? This should be done for all relations which are converted to JSON, maybe some other relation is causing the exception? – Predrag Maric Jul 22 '15 at 09:54
1

You are having this problem because your transaction begins and ends at your service call. Recall from Hibernate documentation that lazy loading only works within a Hibernate Session (in this case, the hibernate session begins and ends with your service call). You can't reconnect to the hibernate session (simply) to initialize the collection.

Two approach to solve this:

  1. You can either change the fetch type to EAGER to eagerly load the questions referenced inside the controller later

  2. Make a separate query to the service to get the questions for the incidence from the controller and set the response in the view bean before returning the response

Additionally as suggestion from a better practise perspective you may want to change the @Transactional annotation in the service method to @Transactional(readOnly = true) for a better performance of the read only call.

Abhishek Gupta
  • 320
  • 2
  • 12
0

What initialize does can you see here what-does-hibernate-initialize-do

Maybe you have to manually initialize it. Please try this:

@Transactional
public List<Incidence> getIncidences() {
  List<Incidence> incidencias = incidencesDao.getIncidences();
  for (Incidence inc : incidencias) {
    inc.getQuestions().size();
  }

  return incidencias;
}
Community
  • 1
  • 1
0

Solved! I had Incidence -> questions relationship in LAZY fetch type (default). However, in the endpoint resource method I was calling getQuestions(). This code wasn't inside the transaction, so it throwed initialization exception.

I have solved it by changing fetch property of @OneToMany to EAGER.

Thanks for your answers. It was difficult for you to help me if you can't see the rest of the code...

Héctor
  • 24,444
  • 35
  • 132
  • 243