74

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.

8
  • The peace of code would help. Simple approach what would help you to return VO object from service or change the FetchType. Commented Apr 12, 2016 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. Commented Apr 12, 2016 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 Commented Apr 12, 2016 at 21:29
  • How do I get the hibernate variable in that model? Commented Apr 12, 2016 at 21:31
  • 1
    Fast solution: myEntity.getListOfThings().size(); Force JPA to initialize collection. Commented Apr 13, 2016 at 11:56

7 Answers 7

159

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
Sign up to request clarification or add additional context in comments.

11 Comments

actually it's an anti-pattern, for further explanation please take a look at vladmihalcea.com/2016/09/05/…
@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 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 en.wikipedia.org/wiki/Anti-pattern).
there are surely another way to fix issue? maybe write directly in jpql the query?
This is interesting information, but the question is more about why "@Transactional" is not enough to keep transaction open imo.
|
12

Adding @Transactional over method works for me

1 Comment

Same, didn't know why some service methods need some methods don't.
4

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.

2 Comments

Well you indeed can avoid the multipleBagException by using Sets rather than Collections or Lists...
Using sets instead of lists is a bad idea according to vladmihalcea.com/hibernate-multiplebagfetchexception
3

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.

2 Comments

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!
Actually just json ignoring the property worked in my case, just keeps it simple
3

just add @Transactional above the method in your service and ensure the import use for Transactional from SpringBoot not from jakarta

1 Comment

Adding @Transactional to the service class instead of individual methods works. Thanks.
0

This isn't the case for the OP, but if you encounter this error with JPA queries, you can add a JOIN FETCH on columns annotated as lazy. This will enable Spring to fetch them eagerly. However, in such cases, it’s often better to remove one of the relationships and fetch it directly in the query. This approach allows other queries to use the entity without repeatedly fetching that particular relationship.

For more information, check out this article on such cases: https://thorben-janssen.com/best-practices-many-one-one-many-associations-mappings/

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
-1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.