62

I have a model that has a pretty large graph of sub entities and hibernate ends up making around 9 statements to lazily fetch all of the data needed but about 4 levels deep I get a "could not initialize proxy - no Session" error and I am not sure why.

Controller

@Transactional(readOnly = true)
@RequestMapping(value = "/v2/plans", method = RequestMethod.GET)
public @ResponseBody List<PlanPresenter> show(HttpServletRequest request) throws Exception {
  List<PlanPresenter> planPresenters = new ArrayList<>();

  CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
  CriteriaQuery<Plan> planQuery = criteriaBuilder.createQuery(Plan.class);
  Root<Plan> root = planQuery.from(Plan.class);

  if (request.getParameter("region") != null || request.getParameter("group") != null) {
    List<Predicate> criteria = new ArrayList<Predicate>();
    if (request.getParameter("region") != null) {
      criteria.add(criteriaBuilder.equal(root.get(Plan_.region), request.getParameter("region")));
    }

    if (request.getParameter("group") != null) {
      criteria.add(criteriaBuilder.equal(root.get(Plan_.groupCode), request.getParameter("group")));
      criteria.add(root.get(Plan_.planSetId).in(groupPlanSetIds));
    } else {
      criteria.add(root.get(Plan_.planSetId).in(currentPlanSetIds));
    }

    Query query = entityManager.createQuery(planQuery.where(criteriaBuilder.and(criteria.toArray(new Predicate[]{}))));

    for (Plan plan : (List<Plan>)query.getResultList()) {
      planPresenters.add(new PlanPresenter(plan));
    }
  }

  return planPresenters;
}

Presenter

public class PlanPresenter {
  public String id;
  public String plan_set_id;
  public String region;
  public String name;
  public String description;
  public HashMap<String, Object> details = new HashMap<String, Object>();

  public PlanPresenter(Plan plan) throws Exception {
    this.id = String.valueOf(plan.id);
    this.plan_set_id = String.valueOf(plan.planSetId);
    this.region = plan.region.trim();
    this.name = plan.getName();
    this.description = plan.getDescription();

    this.details.put("spanish_plan", plan.isSpanishPlan());
    this.details.put("mutually_exclusive", plan.isMutuallyExclusive());
    this.details.put("group_plan", plan.isGroupPlan());
    this.details.put("group_code", plan.groupCode.trim());
    this.details.put("family_plan", plan.isFamilyPlan());
    this.details.put("price", plan.getPrice());
    this.details.put("enrollment_fee", plan.getEnrollmentFee());
    this.details.put("riders", plan.getRiders());
  }
}

Plan

@Entity
public class Plan implements Serializable {
  private static final long serialVersionUID = 7639611964474770505L;

  private static List<String> familyPlanShortNames = Arrays.asList("ABCD");
  @Transient
  private String description = "";

  (Column definitions)

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "XXXX", insertable = false, updatable = false, nullable = true)
  @NotFound(action = NotFoundAction.IGNORE)
  public PlanDetail planDetail;

  @OneToMany(fetch = FetchType.LAZY)
  @JoinColumn(name = "XXXX", insertable = false, updatable = false, nullable = true)
  @OrderBy("XXXX")
  @NotFound(action = NotFoundAction.IGNORE)
  public List<Rider> riders;

  public String getName() {
    return this.planDetail != null ? this.planDetail.longName.trim() : null;
  }

  public Boolean isSpanishPlan() {
    return this.language.trim().equals("ES");
  }

  public Boolean isMutuallyExclusive() {
    return this.mutuallyExclusive.trim().equals("Y");
  }

  public Boolean isGroupPlan() {
    return this.groupCode != null && !this.groupCode.trim().equals("");
  }

  public Boolean isFamilyPlan() {
    return familyPlanShortNames.contains(this.planDetail.shortName.trim());
  }

  public BigDecimal getPrice() {
    return this.planDetail != null ? this.planDetail.price.setScale(2) : null;
  }

  public BigDecimal getEnrollmentFee() {
    return this.planDetail != null ? this.planDetail.enrollmentFee.setScale(2) : null;
  }

  public String getDescription() {
    if (this.planDetail != null && this.planDetail.brochureSections != null) {
      this.planDetail.brochureSections.forEach((brochureSection) -> {
        if (brochureSection.type.trim().equals("P1") && brochureSection.order == 1) {
          this.description = this.description + " " + brochureSection.text.trim();
        }
      });
    }

    return this.description.trim();
  }

  public List<HashMap<String, Object>> getRiders() {
    List<HashMap<String, Object>> riders = new ArrayList<HashMap<String, Object>>();
    if (this.riders != null && this.riders.size() > 0) {
      this.riders.forEach((rider) -> {
        HashMap<String, Object> planRider = new HashMap<String, Object>();
        planRider.put("name", rider.getName());
        planRider.put("price", rider.getPrice());
        planRider.put("description", rider.getDescription());
        riders.add(planRider);
      });
    }
    return riders;
  }
}

Plan Detail

@Entity
public class PlanDetail implements Serializable {
  private static final long serialVersionUID = 2256881691562712018L;

  (Column definitions)

  @OneToMany(fetch = FetchType.LAZY)
  @JoinColumn(name = "XXXX", referencedColumnName = "XXXX", insertable = false, updatable = false, nullable = true)
  @OrderBy("XXXX")
  @NotFound(action = NotFoundAction.IGNORE)
  public List<BrochureSection> brochureSections;
}

Brochure Section

@Entity
public class BrochureSection implements Serializable {
  private static final long serialVersionUID = 1856191232387921427L;

  (Column definitions)
}

Exception

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.models.PlanDetail.brochureSections, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:576) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:215) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:555) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:143) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
at java.lang.Iterable.forEach(Iterable.java:74) ~[?:1.8.0_66]
at com.models.Plan.getDescription(Plan.java:100) ~[classes/:?]
at com.presenters.v2.PlanPresenter.<init>(PlanPresenter.java:20) ~[classes/:?]
at com.controllers.v2.PlansController.show(PlansController.java:64) ~[classes/:?]

Any help would be appreciated.

douglasrlee
  • 663
  • 1
  • 5
  • 7
  • The peace of code would help. Simple approach what would help you to return VO object from service or change the FetchType. – Anton N Apr 12 '16 at 20:26
  • 1
    So, if I change the fetch type to EAGER I can get it to work, BUT I don't really want to for performance reasons. – douglasrlee Apr 12 '16 at 20:44
  • As per the error and the code if fails at brochure section. Can you add hibernate.instance(this.plandetail.getbrochure()) in your getdescription() method. As this attribute is lazy loaded your code in get description cannot find it so in order to use it you have to load it first and do whatever you want to do with it. Please let me know if this helps. Just to point I am also new in hibernate and have encountered this error personally and was able to resolve this way – LearningPhase Apr 12 '16 at 21:29
  • How do I get the hibernate variable in that model? – douglasrlee Apr 12 '16 at 21:31
  • For starters it is imho a bad idea to make your web layer the transactional boundary. That said your controller isn't transactional, there is nothing transaction related in your stack trace. Add `@EnableTransactionManagement` to a configuration class loaded in the same context as the controller (or `` if using XML). If you have that but is loaded by the `ContextLoaderListener` it won't work AOP is only applied in the same application context. – M. Deinum Apr 13 '16 at 06:20
  • links: http://stackoverflow.com/questions/4675303/how-to-force-initialize-a-hibernate-jpa-proxy-to-use-it-in-a-json-call, http://stackoverflow.com/questions/29368563/jpa-equivalent-command-to-hibernate-initialize, http://stackoverflow.com/questions/15359306/how-to-load-lazy-fetched-items-from-hibernate-jpa-in-my-controller, http://www.thoughts-on-java.org/5-ways-to-initialize-lazy-relations-and-when-to-use-them/, http://www.coderanch.com/t/219333/ORM/databases/JPA-force-loading-nested-lazy – Anton N Apr 13 '16 at 11:54
  • 1
    Fast solution: `myEntity.getListOfThings().size();` Force JPA to initialize collection. – Anton N Apr 13 '16 at 11:56
  • Does this answer your question? [How to fix org.hibernate.LazyInitializationException - could not initialize proxy - no Session](https://stackoverflow.com/questions/21574236/how-to-fix-org-hibernate-lazyinitializationexception-could-not-initialize-prox) – Stefnotch Jun 01 '22 at 17:50

5 Answers5

155

If you would like to keep Lazy Load and you are using Spring Boot, just add the config below in your application.properties:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Joao Luiz Cadore
  • 2,656
  • 1
  • 15
  • 11
  • 2
    Just because I am curious. Where did you find this? – douglasrlee Aug 30 '16 at 19:14
  • 50
    actually it's an anti-pattern, for further explanation please take a look at https://vladmihalcea.com/2016/09/05/the-hibernate-enable_lazy_load_no_trans-anti-pattern/ – darkled May 02 '17 at 13:50
  • 3
    @darkled - I think there's actually a difference between "there are potential performance implications that you should be aware of before using this" and "it's an antipattern". Not every application needs the highest possible performance from its database access code, and sometimes not having to spend hours of developer time defining which properties are loaded ahead of time with which queries is more important. – Jules Aug 07 '17 at 07:17
  • 3
    @Jules agreed, not all of the applications need the highest performance, but it's an example of a solution that is usually ineffective and risks being highly counterproductive (which is the definition of the anti-patterns on wikipedia https://en.wikipedia.org/wiki/Anti-pattern). – darkled Aug 07 '17 at 13:22
  • 4
    there are surely another way to fix issue? maybe write directly in jpql the query? – robert trudel Oct 18 '17 at 20:52
  • 22
    This is interesting information, but the question is more about why "@Transactional" is not enough to keep transaction open imo. – Tristan Jul 02 '18 at 15:27
  • 2
    I have the same question as Tristan. The contract of \@Transactional is to keep the transaction and thus the session open until at least by the end of the method annotated by \@Transactional. So this hack shouldn't be needed, and if used, will result in possibly a second transaction being started which is undesirable. – Calicoder Oct 02 '18 at 16:58
  • this solution is an anti-pattern – Rubén Fanjul Estrada Dec 24 '19 at 19:33
  • This solution does work. Can you please provide a bit of explanation here? Also in the link you provided, Vladmi Halcea stated that `Although the hibernate.enable_lazy_load_no_trans configuration property is a lesser-known setting, it’s good to know why you shouldn’t employ it in your data access layer code.` – Chetan Oswal Apr 29 '20 at 03:43
  • 1
    Didn't work for me, but @Transactional did https://stackoverflow.com/a/49900305/426805 – jgreen Dec 30 '20 at 02:41
  • 2
    It's great for use in tests where you select on the repository to assert the properties of an entity: `@SpringBootTest(properties = "spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true")` – andy Mar 19 '21 at 10:43
3

The lazy loading can be kept, without the setting the enable_lazy_load_no_trans parameter. The simplest solution I found was @NamedEntityGraph while using Spring Data JPA. https://www.baeldung.com/spring-data-jpa-named-entity-graphs

The downside was I could not have more than one collection in the @NamedEntityGraph. Adding a second collection resulted in an exception org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags:

Therefore, if you don't want to use the anti-pattern, and only have one collection you are trying to load, the @NamedEntityGraph and @EntityGrpah works with Spring Data JPA.

Bill Snee
  • 119
  • 1
  • 11
3

Adding @Transactional over method works for me

Procrastinator
  • 2,526
  • 30
  • 27
  • 36
Chaman Jain
  • 71
  • 1
  • 2
2

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true it is an anti-pattern and highly recommend to avoid to use it.

could not initialize proxy exception will occur often when child class contains @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) in your relationship.

I recommend to use fetch = FetchType.EAGER in your relationship instead of using LAZY. It is not the best way but it is far better than using anti-pattern.

Nafaz M N M
  • 1,558
  • 2
  • 27
  • 41
  • FetchType.EAGER worked for me. I think your answer should get more votes, it's easy to speak about anti-pattern, but you actually provided an alternative, thanks! – chriszichrisz Jun 29 '22 at 21:07
0

In my case, I had a class with several ManyToOne and ManyToMany relations. Ergo several lists of objects needed to be loaded from database. I resolved the issue by transforming those objects to DTOs. Instead of transferring lists of objects, which may be linked to other objects you pass a list of DTOs that contain only primitives and Strings This is a huge performance booster

mkkabi
  • 611
  • 8
  • 11