Spring Data JPA - "could not initialize proxy - no Session" - With Methods marked as transactional
Asked Answered
D

6

70

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.

Dayfly answered 12/4, 2016 at 20:13 Comment(8)
The peace of code would help. Simple approach what would help you to return VO object from service or change the FetchType.Seagrave
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.Dayfly
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 waySadducee
How do I get the hibernate variable in that model?Dayfly
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 <tx:annotation-driven /> 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.Acetyl
links: #4675803, #29369063, #15359806, thoughts-on-java.org/…, coderanch.com/t/219333/ORM/databases/…Seagrave
Fast solution: myEntity.getListOfThings().size(); Force JPA to initialize collection.Seagrave
Does this answer your question? How to fix org.hibernate.LazyInitializationException - could not initialize proxy - no SessionTimeserver
T
158

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
Trudi answered 1/8, 2016 at 4:14 Comment(11)
Just because I am curious. Where did you find this?Dayfly
actually it's an anti-pattern, for further explanation please take a look at vladmihalcea.com/2016/09/05/…Virgiliovirgin
@Virgiliovirgin - 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.Blague
@Blague 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).Virgiliovirgin
there are surely another way to fix issue? maybe write directly in jpql the query?Rosemari
This is interesting information, but the question is more about why "@Transactional" is not enough to keep transaction open imo.Genniegennifer
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.Amuck
this solution is an anti-patternMeniscus
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.Myrtice
Didn't work for me, but @Transactional did https://mcmap.net/q/281136/-lazyinitializationexception-could-not-initialize-proxy-no-sessionHanse
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")Leong
I
7

Adding @Transactional over method works for me

Incontrovertible answered 5/7, 2022 at 2:9 Comment(1)
Same, didn't know why some service methods need some methods don't.Ut
L
5

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.

Leann answered 22/7, 2020 at 23:44 Comment(2)
Well you indeed can avoid the multipleBagException by using Sets rather than Collections or Lists...Knavery
Using sets instead of lists is a bad idea according to vladmihalcea.com/hibernate-multiplebagfetchexceptionPower
I
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.

Idolism answered 19/9, 2021 at 5:53 Comment(1)
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!Elfrieda
K
2

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

Kilogram answered 17/10, 2023 at 6:28 Comment(1)
Adding @Transactional to the service class instead of individual methods works. Thanks.Tooling
A
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

Autosuggestion answered 1/9, 2023 at 22:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.