Héctor
Héctor

Reputation: 26054

@Transactional is not working in @Service

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.

Upvotes: 4

Views: 1767

Answers (4)

Abhishek Gupta
Abhishek Gupta

Reputation: 330

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.

Upvotes: 1

H&#233;ctor
H&#233;ctor

Reputation: 26054

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...

Upvotes: 0

Alexander Friedrichs
Alexander Friedrichs

Reputation: 106

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;
}

Upvotes: 0

Predrag Maric
Predrag Maric

Reputation: 24423

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;
}

Upvotes: 4

Related Questions