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?