Search code examples
javaspringhibernatespring-mvclazy-initialization

Can't fix failed to lazily initialize a collection of role: could not initialize proxy - no Session


I get org.hibernate.LazyInitializationException when I try to access lazy fetched attributes of my classes, in the jsp forEach call and in the controller class.

I read all the previous answers and tried them, I added @Transactional in my service and dao classes methods that use it just to be sure, I tried adding Hibernate.initialize(), didn't work and later a filter but it makes my perfomance worse than with eager fetch. It works when I change it to eager fetch but I want it to be lazy fetched because of performance. Basically the error happens when absoluteRatingVal() is called at the for loop line. everything in both article and rating is lazy fetch.

Article.java

@Entity
@Table(name = "articles")
public class Article {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "id")
   private int id;

   @Column(name = "title")
   private String title;

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "created_by")
   private User user;

   @OneToMany(cascade = CascadeType.ALL, mappedBy = "article", fetch = FetchType.LAZY)
   private Set<Rating> rating = new HashSet<>();

public Double getAbsoluteRatingVal() {

    double rating = 0;
    Set<Rating> ratings = getRating();
    for (Rating r : ratings) {
        if (Days.daysBetween(new DateTime(r.getDate().getTime()), new DateTime(new Date().getTime()))
                .getDays() < 301)
            if (r.getGenuine() != null)
                rating++;
            else if (r.getOpinion() != null)
                rating++;
    }

    if (ratings.size() > 0)
        return rating;

    return null;
}
}

NewsDao.java with 1 method example that throws this error.

@Repository("newsDao")
@Transactional
@Component("newsDao")
public class NewsDao {
    @Autowired
    private SessionFactory sessionFactory;
    private Transaction tx;

    public Session session() {
        return sessionFactory.openSession();
    }

    @Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
    public List<Article> searchArticlesByRating(String query) {
    Session session = session();
    try {
        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<Article> crit = builder.createQuery(Article.class);
        Root<Article> root = crit.from(Article.class);
        crit.select(root).where(builder.like(root.get("title"), "%" + query + "%"));
        Query<Article> q = session.createQuery(crit);

        List<Article> result = q.list();
        session.close();

        System.err.println(result);         

        Rating fake = new Rating();
        fake.setDate(new Date());
        fake.setFake(new User());
        for (Article a : result) {
            if (a.getAbsoluteRatingVal() == null) 
                a.getRating().add(fake);
        }

        Collections.sort(result,
                Comparator.comparing(Article::getAbsoluteRatingVal).thenComparing(new Comparator<Article>() {
                    public int compare(Article a1, Article a2) {
                        if (a1.getRating() != null && a2.getRating() != null)
                            return a2.getRating().size() - a1.getRating().size();
                        return 0;
                    }
                }));

        for (Article a : result) {
            if (a.getAbsoluteRatingVal() != null && a.getAbsoluteRatingVal().intValue() == 0) 
                a.getRating().remove(fake);
        }

        return result;
    } catch (Exception e) {
        e.printStackTrace();

        session.close();
        return null;
    }
}

Rating.java

@Entity
@Table(name = "ratings")
public class Rating {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "article")
    private Article article;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "voted_genuine")
    private User genuine;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "voted_fake")
    private User fake;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "voted_opinion")
    private User opinion;
 }

EDIT

In featured.jsp I get this error: org.hibernate.LazyInitializationException: could not initialize proxy - no Session when I call article.user.isSubscribed() which accesses an OneToOne relationship, from my understanding user is null because he couldn't be fetched.

featured.jsp

<c:choose>
                            <c:when
                                test='${article.user.isSubscribed() and article.user.subscription.type eq "silver" }'>
                                <a class="bold"
                                    href='${pageContext.request.contextPath}/u/${article.user.username}'><span
                                    class="silvername"> <c:out value="${article.user.name}"></c:out></span></a>
                            </c:when>
                            <c:when
                                test='${article.user.isSubscribed() and article.user.subscription.type eq "gold" }'>
                                <a class="bold"
                                    href='${pageContext.request.contextPath}/u/${article.user.username}'><span
                                    class="goldname"> <c:out value="${article.user.name}"></c:out></span></a>
                            </c:when>
                            <c:when
                                test='${article.user.isSubscribed() and article.user.subscription.type eq "premium" }'>
                                <a class="bold"
                                    href='${pageContext.request.contextPath}/u/${article.user.username}'><span
                                    class="premiumname"> <c:out
                                            value="${article.user.name}"></c:out></span></a>
                            </c:when>
                            <c:otherwise>
                                <a class="bold"
                                    href='${pageContext.request.contextPath}/u/${article.user.username}'><span>
                                        <c:out value="${article.user.name}"></c:out>
                                </span></a>
                            </c:otherwise>
                        </c:choose>

I want the application to run with lazy fetch


Solution

  • Call session.close() immediately before returning from NewsDao#searchArticlesByRating(String) i.e. after Article#getAbsoluteRatingVal() is called.

    @Transactional(...)
    public List<Article> searchArticlesByRating(String query) {
        Session session = session();
        try {
            // business logic here
    
            session.close();   
    
            return result;
    
        } catch (Exception e) {
        }
    }