1

I have a set of simple models like this (getters and setters omitted for brevity):

@Entity
public class Customer {
  @Id 
  private Integer id;
}

@Entity
public class Order {
  @Id
  private Integer id;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "customer_id")
  private Customer customer;
}

I am trying to load an Order using a Spring JPA repository with a findById method, including the customer.

First I tried this:

@Transactional
Optional<Order> findById(Integer id) {
    return repository.findById(id);
}

But when I tried to access Customer I got a LazyInitializationException: could not initialize proxy - no Session. So after referring to some other questions, I updated my method to be a bit uglier, but to explicitly call Hibernate.initialize:

@Transactional
Optional<Order> findById(Integer id) {
    return repository.findById(id)
                     .map( order -> {
                         Hibernate.initialize(order.getCustomer());
                         return order;
                     );
}

But I still get org.hibernate.LazyInitializationException: could not initialize proxy - no Session. repository is a regular CrudRepository which provides the findById method out-of-the-box.

How can I initialize this lazily loaded child entity? My understanding is that the @Transactional indicates that I should still be within the transaction for the entirety of this method call. The only thing further downstream is the repository itself, which is just an interface, so I'm not sure how else to go about forcing the load of this child entity.

The Order entity and everything else in it is retrieved properly from the database; it's only when I try to get the lazy-loaded child entities that we start having issues.

The only way I managed to get this working was to write a custom hql method in the Repository using a left join fetch. While that works, it clutters up my repository with a method that is pretty much a duplicate of another and which I'm pretty sure I'm not actually supposed to need (so I would rather not do it this way.)

Spring-Boot 2.1.4.RELEASE, Spring 5.1.6.RELEASE, Hibernate 5.3.7.Final.

Roddy of the Frozen Peas
  • 11,100
  • 9
  • 37
  • 73
  • What class does contain the `findById` method? Does it implement any `interface` or is it just a `class`? Does your configuration contains something like `spring.aop.proxy-target-class=false`? Have you changed the AOP configuration in java config? – Denis Zavedeev Apr 11 '19 at 20:13
  • My repository is just a JPA repository interface that extends CrudRepository. The method is provided directly by the CrudRepository interface. I haven't implemented any custom repositories and am relying on Spring JPA to generate the implementation. I haven't touched anything related to AOP config or otherwise. – Roddy of the Frozen Peas Apr 11 '19 at 20:24
  • Sorry, I mean the `findById` method that *invokes the repository* – Denis Zavedeev Apr 11 '19 at 20:26
  • Do you have enabled Spring transactional management ? [Documentation](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#tx-decl-explained) – Baptiste Beauvais Apr 11 '19 at 20:33
  • Yes, I have annotated `@EnableTransactionManagement` on my datasource configuration. All the cascading saves seem to use it properly, too. – Roddy of the Frozen Peas Apr 11 '19 at 20:37
  • @caco3 There is another method in the same class that invokes the findById method. It does a transform to another data type that I will pass to the web layer as a response to an HTTP GET. The calling method is not annotated with `@Transactional` which is why I was trying to get the findById method itself to do the eager fetch. – Roddy of the Frozen Peas Apr 11 '19 at 20:39
  • @RoddyoftheFrozenPeas check out [this](https://stackoverflow.com/questions/5109343/transactional-method-called-from-another-method-doesnt-obtain-a-transaction) question – Denis Zavedeev Apr 11 '19 at 20:45
  • @caco3 - I have a controller call a dao method, which calls the dao's transactional method. If I have the controller call the transactional method directly, then I don't get a tx and the SELECT fails entirely. The way I have it working currently gets a tx but it appears to be only scoped for the repository call itself. – Roddy of the Frozen Peas Apr 12 '19 at 15:30

1 Answers1

0

You have to define the method as public. See "Method visibility and @Transactional" in the spring docs.

This should work:

@Transactional
public Optional<Order> findById(Integer id) {
    Optional<Order> order = repository.findById(id);
    order.ifPresent(o -> Hibernate.initialize(o.getCustomer()));
    return order;
}
dpelisek
  • 616
  • 3
  • 11
  • 20