0

I'm working on a web application in the Vaadin framework to retrieve some data from a MySQL database back-end. The application itself is a survey system to keep track of software consultants skills (and rate them). Some colleagues of mine had already written the back-end code for this. Now that I am trying to couple the Vaadin front-end to it, I'm running into a quite annoying issue. I have two DAO classes, PersonDAO and SurveyDAO.

PersonDAO is called first, afterwards the SurveyDAO is called. PersonDAO retrieves the Session from a HibernateUtil class (not written by myself) which is stored in a ThreadLocal Session variable. When SurveyDAO does exactly the same, the ThreadLocal Session.get() method returns a null reference! After some reading I know Vaadin has strange ways of working with Sessions. But I cannot see how I can fix this, or how there is another way of working with the DAO's then.

Here's the full walk through the code:

In my main UI, upon loading the webapp, I create a ComboBox component which is filled by a List returned by a PersonDAO. As so:

private ComboBox createConsultantsComboBox() {

    ComboBox comboBox = new ComboBox("Surveyed consultants:");

    BeanItemContainer<Person> consultantContainer = new BeanItemContainer<>(Person.class);
    consultantContainer.addAll(personDAO.findAll());
    comboBox.setContainerDataSource(consultantContainer);

    comboBox.addValueChangeListener(new ConsultantSelectedListener());

    return comboBox;

}

This DAO works seamlessly. However, when the Value in that ComboBox changes (a consultant is selected), I use that Person's id to fetch a List<Survey> from a SurveyDAO of that consultants surveys. This is done using the following code, which is a ValueChangeListener attached to the ComboBox filled with Person objects:

private class ConsultantSelectedListener implements ValueChangeListener {

    @Override
    public void valueChange(ValueChangeEvent event) {
        if (event.getProperty().getValue() != null) {
            Person selectedConsultant = (Person) event.getProperty().getValue();
            leftPanel.addComponent(createSurveysComboBox(selectedConsultant.getId()));
        }
    }
}

This is where things go wrong. The createSurveysComboBox() method works the same way as the createConsultantsComboBox() method, apart from the fact that it has an int id parameter passed to forward to the SurveyDAO, as it is needed in a query as parameter. However, when the SurveyDAO calls findByPersonId, I get a NullPointerException and the following stack trace print:

jun 30, 2016 10:15:27 AM com.vaadin.server.DefaultErrorHandler doDefault
SEVERE: 
java.lang.NullPointerException
    at be.kapture.util.HibernateUtil.getSession(HibernateUtil.java:55)
    at be.kapture.dao.AbstractDAO.getCurrentSession(AbstractDAO.java:16)
    at be.kapture.dao.SurveyDAO.findByPersonId(SurveyDAO.java:24)
    at be.kapture.web.ConsultantSkillsUI.createSurveysComboBox(ConsultantSkillsUI.java:92)
    at be.kapture.web.ConsultantSkillsUI.access$100(ConsultantSkillsUI.java:27)
    at be.kapture.web.ConsultantSkillsUI$ConsultantSelectedListener.valueChange(ConsultantSkillsUI.java:82)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:508)
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:198)
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:161)
    at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:1008)
    at com.vaadin.ui.AbstractField.fireValueChange(AbstractField.java:1159)
    at com.vaadin.ui.AbstractField.setValue(AbstractField.java:570)
    at com.vaadin.ui.AbstractSelect.setValue(AbstractSelect.java:732)
    at com.vaadin.ui.AbstractField.setValue(AbstractField.java:468)
    at com.vaadin.ui.ComboBox.changeVariables(ComboBox.java:730)
    at com.vaadin.server.communication.ServerRpcHandler.changeVariables(ServerRpcHandler.java:603)
    at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:422)
    at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:273)
    at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:79)
    at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1409)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:364)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2508)
    at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2497)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

findByPersonId code can be found above. I'll now supply the code snippets for the AbstractDAO.getCurrentSession() and HibernateUtil.getSession() methods (which, again, I have not written myself - hence why I cannot figure out what's wrong!)

AbstractDAO

package be.kapture.dao;

import org.hibernate.Session;

import be.kapture.util.HibernateUtil;

public abstract class AbstractDAO<T> {

    final Class<T> typeParameterClass;

    public AbstractDAO(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public Session getCurrentSession() {
        return HibernateUtil.getSession();
    }

    public void create(T t) {
        getCurrentSession().save(t);
    }

    public void update(T t) {
        getCurrentSession().update(t);
    }

    public void delete(T t) {
        getCurrentSession().delete(t);
    }

    public T read(int id) {
        return (T) getCurrentSession().get(typeParameterClass, id);
    }

}

HibernateUtil

package be.kapture.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

import java.util.Objects;

public class HibernateUtil {

    private static final SessionFactory sessionFactory = buildSessionFactory();
    private static final ThreadLocal<Session> sessionManagers = buildSessionManagers();

    private static SessionFactory buildSessionFactory() {
        try {
            StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
                    .configure("/hibernate.cfg.xml").build();

            Metadata metadata = new MetadataSources(standardRegistry).addResource("be/kapture/entities/Person.hbm.xml")
                    .addResource("be/kapture/entities/Skill.hbm.xml")
                    .addResource("be/kapture/entities/SkillGroup.hbm.xml")
                    .addResource("be/kapture/entities/SkillNature.hbm.xml")
                    .addResource("be/kapture/entities/Survey.hbm.xml")
                    .addResource("be/kapture/entities/SurveyDetail.hbm.xml").getMetadataBuilder()
                    .applyImplicitNamingStrategy(ImplicitNamingStrategyJpaCompliantImpl.INSTANCE).build();

            SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
            return sessionFactory;

        } catch (Throwable ex) {
            System.err.println("initial SessionFactory creation failed " + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }


    private static ThreadLocal<Session> buildSessionManagers() {
        ThreadLocal<Session> sessionManagers = new ThreadLocal<>();
//      System.out.println("sessionFactory.getCurrentSession == null ??");
//      System.out.println(sessionFactory.getCurrentSession() == null);
        sessionManagers.set(sessionFactory.getCurrentSession());
        return sessionManagers;
    }

    public static Session getSession() {
//      Objects.requireNonNull(sessionFactory, "sessionFactory was null");
//      Objects.requireNonNull(sessionManagers, "sessionManagers was null");
//      System.out.println(sessionManagers.get());
        Session session = sessionManagers.get();
//      Objects.requireNonNull(session, "session was null");
        if(!session.isOpen()){
            sessionManagers.remove();
            session = sessionFactory.getCurrentSession();
            sessionManagers.set(session);           
        }
        session.beginTransaction();
        return session;
    }

}

In the session retrieval methods you can see some debugging output. When uncommented, it is the session variable in HibernateUtil.getSession() method that will trigger, because the ThreadLocal returned a null reference.

Why is this?

halfer
  • 19,824
  • 17
  • 99
  • 186
Yannick Thibos
  • 93
  • 2
  • 11
  • Reading into the org.Hibernate.Session JavaDoc: "It is not intended that implementors be threadsafe. Instead each thread/transaction should obtain its own instance from a SessionFactory.". Does this mean that the HibernateUtil.getSession method should be rewritten to fetch a new Session from the Factory on each call? – Yannick Thibos Jun 30 '16 at 09:18
  • Did you read about the `ThreadLocal` concept? The Hibernate Session is not required to be thread-safe when you use the ThreadLocal correctly. But you need to ensure that is was initialized once per thread. And you should close it, e.g. when the request ends. Additionally, see [here](https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch02.html#d5e737) for patterns on sessions. – Steffen Harbich Jun 30 '16 at 09:37
  • As you wrote your comment, I was writing an answer to my own question. While doing so, I figured out exactly what you just said. How do I apply this in a Vaadin setting though? I've read that Vaadin does not really work with the classic request system, but instead uses multiply concurrent threads asynchronously to synch client content with the server. Am I wrong? – Yannick Thibos Jun 30 '16 at 09:50

1 Answers1

0

The problem is solved by indeed asking the SessionFactory for a new Session on each transaction made - as described in the Hibernate Session JavaDoc. Implemented in the code from the question this was the only change required to have made it work:

In HibernateUtil

public static Session getSession() {

        return sessionFactory.openSession();

}

The AbstractDAO (and thus all DAO's inheriting it) will call its method getCurrentSession - which should thus be better named getNewSession. Calling code should start a new transaction on it, perform its database operations and commit/rollback accordingly.

However, I get the feeling that this is a sloppy way to handle Sessions. I've not read anything about the SessionFactory closing its sessions itself. The JavaDoc also says that implementors of the Factory should be made thread safe. Hibernate SessionFactory JavaDoc here

I think someone can enhance this answer with a better solution. Feel free to do so.

Yannick Thibos
  • 93
  • 2
  • 11
  • Care to close DB sessions when you don't need them any longer. Otherwise you are running into memory leaks or - if you have a connection manager - connection leaks making your application hanging. – Steffen Harbich Jun 30 '16 at 09:50
  • I'm thinking I'm better off splitting this back-end code into a Data Acces layer and a Service layer (as should have been done in the first place anyway). I'm going to have the AbstractDAO provide methods to ask for a transaction - which in turn fetches a new Session - and one to close the transaction (which closes the session in the process). This keeps session management within the DAO's. Would that be a proper way? – Yannick Thibos Jun 30 '16 at 10:01