使用 Vaadin Hibernates ThreadLocal<Session>.get() 在第一次调用后返回 null
Posted
技术标签:
【中文标题】使用 Vaadin Hibernates ThreadLocal<Session>.get() 在第一次调用后返回 null【英文标题】:Using Vaadin Hibernates ThreadLocal<Session>.get() returns null after first call 【发布时间】:2016-11-02 05:48:23 【问题描述】:我正在开发 Vaadin 框架中的 Web 应用程序,以从 mysql 数据库后端检索一些数据。该应用程序本身是一个调查系统,用于跟踪软件顾问的技能(并对其进行评分)。我的一些同事已经为此编写了后端代码。现在我正在尝试将 Vaadin 前端与其耦合,我遇到了一个非常烦人的问题。我有两个 DAO 类,PersonDAO 和 SurveyDAO。
首先调用 PersonDAO,然后调用 SurveyDAO。 PersonDAO 从存储在 ThreadLocal Session 变量中的 HibernateUtil 类(不是我自己编写的)中检索 Session。当 SurveyDAO 做同样的事情时,ThreadLocal Session.get() 方法返回一个空引用!经过一番阅读,我知道 Vaadin 使用 Sessions 的方式很奇怪。但是我看不出我该如何解决这个问题,或者当时如何使用 DAO 的另一种方式。
下面是完整的代码:
在我的主 UI 中,加载 web 应用程序后,我创建了一个 ComboBox 组件,该组件由 PersonDAO 返回的 List 填充。因此:
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;
这个 DAO 可以无缝运行。但是,当该 ComboBox 中的值更改(选择了顾问)时,我使用该人员的 ID 从该顾问调查的 SurveyDAO 中获取List<Survey>
。这是使用以下代码完成的,这是一个附加到填充有 Person 对象的 ComboBox 的 ValueChangeListener:
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()));
这就是出现问题的地方。 createSurveysComboBox() 方法与 createConsultantsComboBox() 方法的工作方式相同,除了它具有传递给 SurveyDAO 的 int id 参数这一事实,因为在查询中需要它作为参数。但是,当 SurveyDAO 调用 findByPersonId 时,我得到一个 NullPointerException 和以下堆栈跟踪打印:
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 代码可以在上面找到。我现在将为 AbstractDAO.getCurrentSession() 和 HibernateUtil.getSession() 方法提供代码 sn-ps(这也是我自己没有编写的 - 因此我无法弄清楚出了什么问题!)
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;
在会话检索方法中,您可以看到一些调试输出。取消注释时,将触发 HibernateUtil.getSession()
方法中的 session 变量,因为 ThreadLocal 返回了一个空引用。
这是为什么?
【问题讨论】:
阅读 org.Hibernate.Session JavaDoc:“实现者并不是线程安全的。相反,每个线程/事务都应该从 SessionFactory 获取自己的实例。”。这是否意味着应该重写 HibernateUtil.getSession 方法以在每次调用时从 Factory 中获取新的 Session? 你读过ThreadLocal
这个概念吗?当您正确使用 ThreadLocal确实通过在每个事务上向 SessionFactory 询问新 Session 来解决问题 - 如Hibernate Session JavaDoc 中所述。在问题的代码中实现,这是使其工作所需的唯一更改:
在 HibernateUtil 中
public static Session getSession()
return sessionFactory.openSession();
AbstractDAO(以及所有继承它的 DAO)将调用它的方法 getCurrentSession - 因此应该更好地命名为 getNewSession。调用代码应该在其上启动一个新事务,执行其数据库操作并相应地提交/回滚。
但是,我觉得这是处理 Sessions 的一种草率方式。我还没有阅读任何关于 SessionFactory 关闭其会话本身的信息。 JavaDoc 还说工厂的实现者应该是线程安全的。 Hibernate SessionFactory JavaDoc here
我认为有人可以通过更好的解决方案来增强这个答案。随意这样做。
【讨论】:
当您不再需要数据库会话时,请注意关闭它们。否则,您会遇到内存泄漏,或者 - 如果您有连接管理器 - 连接泄漏会导致您的应用程序挂起。 我想我最好将这个后端代码拆分为一个数据访问层和一个服务层(无论如何应该首先完成)。我将让 AbstractDAO 提供方法来请求一个事务——这反过来又获取一个新的会话——以及一个关闭事务(关闭进程中的会话)的方法。这将会话管理保持在 DAO 中。这会是正确的方法吗?以上是关于使用 Vaadin Hibernates ThreadLocal<Session>.get() 在第一次调用后返回 null的主要内容,如果未能解决你的问题,请参考以下文章