1
votes

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
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; } - b15
Try doing @OneToMany(mappedBy="user",fetch = FetchType.LAZY)` and in @ManyToOne(fetch = FetchType.LAZY)`. - SRy
no luck, same result - b15

4 Answers

2
votes

@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();
}
2
votes

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.

1
votes

@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.

0
votes

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();
    }