Hibernate isInitialized outside of session

Viewed 257

Is there any way to check whether an entity's lazy collection attribute is initialized once we're outside the Transactional session?

For example, my Job entity has a lazy association to a list of Step, which is only sometimes initialized. In my controller, I would like to know if the steps collection has been initialized, although I'm met with a LazyInitializationException instead:

org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [com.overflow.stack.Job.steps] - no session and settings disallow loading outside the Session
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.throwLazyInitializationException(EnhancementHelper.java:199) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:89) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.loadAttribute(LazyAttributeLoadingInterceptor.java:76) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.fetchAttribute(LazyAttributeLoadingInterceptor.java:72) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.handleRead(LazyAttributeLoadingInterceptor.java:53) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at com.overflow.stack.Job.$$_hibernate_read_steps(Job.java) ~[classes/:?]
    at com.overflow.stack.Job.getSteps(Job.java:135) ~[classes/:?]
    ...
@Entity
public class Step {
  ...
}

@Entity
public class Job {
  @OneToMany(mappedBy = "job", fetch = FetchType.LAZY)
  private List<Step> steps = new ArrayList<>();

  public List<Step> getSteps() {
    return steps;
  }
  ...
}

@Controller
public JobController {
  public List<Step> getJobSteps(Long id) {
    // Transactional service class method which fetches by id (does not initialize steps)
    Job job = jobService.getJob(id);

    if (Hibernate.isInitialized(job.getSteps())) { // Throws LazyInitializationException
      return job.getSteps();
    } else {
      return List.of();
    }
  }
}

@Service
@Transactional
public JobService {
  @Autowired JobRepository jobRepo;

  public Job getJob(Long id) {
    Job job = jobRepo.findById(id).get();
    Hibernate.unproxy(job);
    return job;
  }
}

In this case, the exception is thrown when it attempts to execute job.getSteps() in the if statement. I know that the session is closed by the time I leave the service call (the controller is not Transactional), although I'm wondering if there's any way to check if it's been initialized, or is the getSteps() method completely out of reach at that point?

1 Answers

Hibernate.isInitialized(job.getSteps()) shouldn't throw LazyInitializationException (LIE).

job.getSteps() returns a proxy, to have LIE you need to call a method of a proxy, for example job.getSteps().size().

job.getSteps() can throw LIE, if job is a proxy itself.

Anyway using entities as a response of REST API is a very bad idea. You will have a lot of issues with that.

Better to use JobEntity for an entity and Job for DTO. Just convert from JobEntity to Job on the service level. You can call Hibernate.isInitialized(jobEntity.getSteps()) on the service level and set an empty collection for the steps to the Job.

To unproxy entity

Hibernate.unproxy() returns a different object (not the same proxy). Also keep in mind that "If the proxy is uninitialized, it automatically triggers an initialization".

public Job getJob(Long id) {
    Job job = jobRepo.findById(id).get();
    return Hibernate.unproxy(job, Job.class);
}
Related