9

Having a hard time getting a simple OneToMany mapped by the ManyToOne Entity to work when I go to fetch a user's comments. Other answers have suggested that you have to create the query yourself with the entityManager but that seems so horrible. Whats the point of an ORM if you can't even do something simple like this without hardcoding inline sql? Seems more likely that I'm doing something wrong.

Seems like maybe it has something to do with the fact that I'm accessing the user.getComments() method from jsp using the model. Not sure what the best way to do this is.

Schema:

CREATE TABLE users (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE comments (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    comment_text VARCHAR(255) NOT NULL,
    photo_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY(user_id) REFERENCES users(id)
);

User Controller method:

@RequestMapping("/user")
public ModelAndView getUser(@RequestParam int id) {
    return new ModelAndView("user", "message", userService.getUser(id));
}

UserService:

@Service
public class UserService {

    private UserDAO userDAO;

    @Autowired
    public UserService(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Transactional
    public User getUser(int id) {
        return userDAO.getUser(id);
    }

}

UserDAO:

@Repository
public class UserDAO {

    @PersistenceContext
    EntityManager entityManager;

    @Nullable
    public User getUser(int id)
    {
        return entityManager.find(User.class, id);
    }
}

User entity:

@Entity
@Table(name="users")
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="username")
    private String userName;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="created_at")
    private Date createdAt;

    @OneToMany(mappedBy="user")
    private List<Comment> comments;

    public int getId() {
        return id;
    }

    public String getUserName() {
        return userName;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    @Transactional
    public List<Comment> getComments() {
        return comments;
    }
}

Comment entity:

@Entity
@Table(name="comments")
public class Comment {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="comment_text")
    private String commentText;

    @Column(name="photo_id")
    private int photoId;

    @ManyToOne
    @JoinColumn(name="user_id")
    private User user;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at")
    private Date createdAt;

    @Override
    public String toString() {
        return "Comment [id=" + id + ", commentText=" + commentText + ", photoId=" + photoId + ", userId=" + user.getId()
                + ", createdAt=" + createdAt + "]";
    }

    public int getId() {
        return id;
    }

    public String getCommentText() {
        return commentText;
    }

    public int getPhotoId() {
        return photoId;
    }

    public User getUser() {
        return user;
    }

    public Date getCreatedAt() {
        return createdAt;
    }
}

Stacktrace:

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/test] threw exception [An exception occurred processing [WEB-INF/jsp/user.jsp] at line [34]

31: User 32: Creation date 33: 34: 35: 36: 37:

Stacktrace:] with root cause org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.instagramviewer.entity.User.comments, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217) at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581) at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148) at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:303) at org.apache.taglibs.standard.tag.common.core.ForEachSupport.toForEachIterator(ForEachSupport.java:348) at org.apache.taglibs.standard.tag.common.core.ForEachSupport.supportedTypeForEachIterator(ForEachSupport.java:224) at org.apache.taglibs.standard.tag.common.core.ForEachSupport.prepare(ForEachSupport.java:155) at javax.servlet.jsp.jstl.core.LoopTagSupport.doStartTag(LoopTagSupport.java:256) at org.apache.jsp.WEB_002dINF.jsp.user_jsp._jspx_meth_c_005fforEach_005f0(user_jsp.java:285) at org.apache.jsp.WEB_002dINF.jsp.user_jsp._jspService(user_jsp.java:172) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:476) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384) at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:170) at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1370) at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1116) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:668) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)

4
  • Possible duplicate of Hibernate LazyInitializationException while using collection in Spring JSP page Commented Feb 17, 2019 at 19:12
  • Tried changing my service method to initialize getComments. Hibernate.initialize() is throwing the LazyInitializationException now. @Transactional public User getUser(int id) { User user = userDAO.getUser(id); Hibernate.initialize(user.getComments()); return user; } Commented Feb 17, 2019 at 19:21
  • Try doing @OneToMany(mappedBy="user",fetch = FetchType.LAZY)` and in @ManyToOne(fetch = FetchType.LAZY)`. Commented Feb 17, 2019 at 20:33
  • no luck, same result Commented Feb 17, 2019 at 20:39

4 Answers 4

4

I did two things wrong:

First: I forgot to enable transaction management in my spring java config class

@Configuration
@EnableTransactionManagement
public class AppConfig {

  @Bean
  public LocalContainerEntityManagerFactoryBean factoryBean() {
      LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
      factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);

      return factory;
  }

  @Bean
  public PlatformTransactionManager transactionManager(){
     JpaTransactionManager transactionManager = new JpaTransactionManager();
     transactionManager.setEntityManagerFactory(factoryBean().getObject() );
     return transactionManager;
  }
}

Second: As Vlad said, I needed to initialize the list of Comments as part of the transaction in my service layer

@Transactional
public User getUser(int id) {
    User user = userDAO.getUser(id);
    Hibernate.initialize(user.getComments());
    return user;
}

Now I can avoid the join fetch solution and this only adds a single line of java code anywhere in the service layer that I want to retrieve the list of comments.

Sign up to request clarification or add additional context in comments.

1 Comment

Started working on an old project, and there same thing is also happened. After wasting a lot of time, got your answer now. Thanks a lot!
2

@Transactional will not help in your because, when you're trying to load comments(on JSP level) transaction already closed(it opened on start of getUser method and closed on finish). The possible way is to initalize your comments inside transaction.

@Transactional
public User getUser(int id) {
    User user = userDAO.getUser(id);
    user.getComments().size(); //initializing
    return user;
}

It is the most primitive way but in should work for you. If it works you can read about Named Entity Graph later. It is more advanced way to initialize needed lazy associations.

9 Comments

That and Hibernate.initialize(user.getComments()); instead of .size() both threw the LazyInitializationException. Really confusing.
Which @Transactional are you using(from which package)?
org.springframework.transaction.annotation.Transactional
Use @Transactional from javax package. Secondly, you need to invoke Hibernate#initialize on each proxy, not on the list.
@Andronicus why use that @Transactional? And what does initializing on each proxy look like?
|
2

@Vlad already noticed, that you're referencing Comments outside of the transaction. Since you're using jpql, you can eagerly fetch User together with his comments:

public List<User> getAllUsers()
{
    TypedQuery<User> query = entityManager.createQuery("SELECT e from User e left join fetch e.comments", User.class);
    return (List<User>) query.getResultList();
}

4 Comments

I shouldn't have included getAllUsers in my code because it's not called by the jsp. getUser(id) is the one that I want to show the comments for. I'll try to adapt this solution to a single user retrieved by id
I'll accept this for now. I really don't like the idea that this is necessary and I'm not sure I believe it.
I think, that's great, you're doubting. If you find a better solution, you can post it here, I'm always glad to learn. Cheers mate.
Yea thanks I appreciate it. At this point I just need to try to understand why @Transactional is incapable of handling the session itself.
0

I was trying to avoid doing this from the beginning but I guess the best way to keep the entity's fetch type lazy is to create a method in the DAO layer that eagerly fetches using join fetch.

@Nullable
    public User getUser(int id)
    {
        TypedQuery<User> query = entityManager.createQuery("SELECT e from User e left join fetch e.comments c where e.id = :id", User.class);
        query.setParameter("id", id);
        return query.getSingleResult();
    }

2 Comments

If there's another way that doesn't involve this ugly hardcoded query crap I'll accept the answer.
You have duplicated my answer using left join fetch. Please accept any of them to help others with same problem.

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.