3

I have User and Post entities with a unidirectional relationship. I am getting org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: exception when I try to get all posts from a specific user.

According to these SO answers, the optimal way to handle this is to use @Transactional annotation for the service method/class. Placing annotations does not work for me. I am using Wildfly server, Hibernate, MySQL, and Java EE MVC web framework.

How do I make it work, i.e. get the posts from a user? I managed to do it via eager loading, but this is not recommended for performance reasons.

@Transactional
public class UserService {

    private List<User> users;
    private Set<Post> posts;


    @PersistenceContext(unitName = "my-pu")
    private EntityManager em;

    public List<User> getUsers() {

        String qlQuery = "SELECT u FROM User u";
        Query query = em.createQuery(qlQuery);
        users = query.getResultList();

        return users;
    }

    @Transactional
    public Set<Post> getUserPosts(Long userId) {

            String qlQuery = "SELECT u FROM User u WHERE u.id = :userId";
            Query query = em.createQuery(qlQuery);
            query.setParameter("userId", userId);
            User user = (User) query.getSingleResult();

            posts = user.getPosts();

        return posts;
    }
}

This is my Service method.

@Path("users")
@Controller
public class UserController {

    @Inject
    private Models models;

    @Inject
    private UserService service;

    @GET
    @Produces("text/html")
    @View("showUsers.ftl")
    public void users() {

        List<User> users = service.getUsers();

        models.put("users", users);
    }

    @GET
    @Path("{id}")
    @Produces("text/html")
    @View("showUserPosts.ftl")    
    public void getPosts(@PathParam("id") Long userId) {

        System.out.println("here am i");

        Set<Post> posts = service.getUserPosts(userId);

        models.put("posts", posts);
    }
}

This is my controller.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
            <persistence-unit name="my-pu" transaction-type="JPA">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/testdb?useSSL=false"/>
            <property name="javax.persistence.jdbc.user" value="testuser"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.password" value="test623"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
            <property name="hibernate.dialect.storage_engine" value="innodb"/>

            <property name="javax.persistence.schema-generation.database.action"
                      value="drop-and-create"/>      
            <property name="javax.persistence.sql-load-script-source"
                      value="META-INF/sql/data.sql" />        
        </properties>
    </persistence-unit>
</persistence>

An this is my persistence unit.

Error message:

org.jboss.resteasy.spi.UnhandledException: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.zetcode.model.User.posts, could not initialize proxy - no Session
    at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:257)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:195)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:539)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:461)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:231)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:137)
    at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:361)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:140)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:217)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:67)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
Jan Bodnar
  • 10,969
  • 6
  • 68
  • 77
  • 3
    Why does your method query for an `User`, when it should be querying the posts? It's really convoluted to load a `User`, then get its posts and throw away the user. You should do a `SELECT p FROM Post p ...` or `SELECT u.posts FROM User u ...` instead. – Kayaman May 21 '18 at 10:14
  • Thanks, noted. It simply was more straightforward for me. – Jan Bodnar May 21 '18 at 10:25
  • What's after `role:` in the error message you posted? – Dragan Bozanovic May 25 '18 at 10:42
  • It's `com.zetcode.model.User.posts, could not initialize proxy - no Session` I have appended a stack trace. – Jan Bodnar May 25 '18 at 13:47

1 Answers1

1

You are returning an uninitialized collection proxy out of session boundaries (demarcated by the transaction in this case).

You can initialize the proxy by calling Hibernate.initialize(posts) or by just calling posts.size() before returning it (the first approach more clearly describes the intent imo).

An alternative that I use most often is to utilize DTOs, so that I don't have to deal with detached objects, but also because of the ability to tailor presentation objects (and web service responses in general) to the exact needs of clients using them. Also that way the domain model (Hibernate entities) is decoupled from the presentation logic, allowing the two to evolve independently.

Other alternatives include Open Session in View (anti-?) pattern and hibernate.enable_lazy_load_no_trans property, but they are less flexible and have their own pros and cons.

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • The issue is resolved, this has been recognized as a bug and is resolved by Hibernate, there is a pull request and should be part of Hibernate 5.4.1. See Jira issue 13191 - https://hibernate.atlassian.net/browse/HHH-13191 – Tim Mickelson Jan 10 '19 at 19:34
  • Workaround for earlier version Hibernate, add hibernate.jpa.compliance.proxy=false – Tim Mickelson Jan 10 '19 at 19:34