2016-06-30 61 views
0

我正在研究Vaadin框架中的Web應用程序以從MySQL數據庫後端檢索一些數據。應用程序本身是一個調查系統,用於跟蹤軟件顧問的技能(並評估他們)。我的一些同事已經爲此寫了後端代碼。現在我正試圖將Vaadin前端連接到它,我遇到了一個非常煩人的問題。我有兩個DAO類,PersonDAO和SurveyDAO。使用Vaadin Hibernates ThreadLocal <Session> .get()在第一次調用後返回null

先調用PersonDAO,然後調用SurveyDAO。 PersonDAO從存儲在線程本地會話變量中的HibernateUtil類(不是我自己寫的)中檢索會話。當SurveyDAO完全相同時,ThreadLocal Session.get()方法返回空引用!經過一番閱讀後,我知道Vaadin與Sessions合作有奇怪的方式。但是我看不出我能如何解決這個問題,或者當時還有另一種與DAO合作的方式。

這裏是通過代碼完整的步行路程:

在我的主界面,在加載web應用程序,我創建一個由由的PersonDAO返回一個列表填充ComboBox組件。如下所示:

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無縫工作。但是,當組合框中的值發生變化(選擇了顧問)時,我使用該人員的ID從SurveyDAO中獲取List<Survey>的顧問調查。

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

這是出問題:這是使用下面的代碼,這是連接到充滿Person對象的組合框一個ValueChangeListener完成。 createSurveysComboBox()方法與createConsultantsComboBox()方法的工作方式相同,但它有一個int id參數傳遞給SurveyDAO,因爲它在查詢中作爲參數需要。然而,當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()方法(同樣,我沒有寫我自己 - 所以爲什麼我無法弄清楚什麼是錯的)

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()將觸發的方法,因爲ThreadLocal返回空引用。

這是爲什麼?

+0

讀入org.Hibernate.Session JavaDoc: 「實現者並不打算使用線程安全,而是每個線程/事務都應該從SessionFactory獲取它自己的實例。」這是否意味着HibernateUtil.getSession方法應該被重寫,以便在每次調用時從Factory中獲取一個新的Session? –

+0

您是否閱讀過'ThreadLocal'概念?當您正確使用ThreadLocal 時,Hibernate會話不需要是線程安全的。但是你需要確保每個線程初始化一次。你應該關閉它,例如當請求結束時。此外,請參閱[此處](https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch02.html#d5e737)瞭解會話中的模式。 –

+0

當你寫下你的評論時,我正在爲自己的問題寫一個答案。在這樣做的時候,我明白你剛纔所說的是什麼。我如何在Vaadin環境中應用這個?我讀過Vaadin並沒有真正使用傳統的請求系統,而是使用多個併發線程異步同步客戶端內容與服務器。我錯了嗎? –

回答

0

問題的解決方法確實是在SessionFactory上爲每個事務創建新會話,如Hibernate Session JavaDoc中所述。在代碼中實現從問題這是唯一的變化需要使人們的工作:

HibernateUtil中

public static Session getSession() { 

     return sessionFactory.openSession(); 

} 

的AbstractDao的(以及所有DAO的繼承它)將調用其方法getCurrentSession - 這應該更好地命名爲getNewSession。調用代碼應該啓動一個新的事務,執行它的數據庫操作並相應地提交/回滾。

但是,我覺得這是一個馬虎的方式來處理會議。我沒有閱讀關於SessionFactory自己關閉會話的任何內容。 JavaDoc還說工廠的執行者應該製造線程安全Hibernate SessionFactory JavaDoc here

我認爲有人可以用更好的解決方案來增強這個答案。隨意這樣做。

+0

當您不再需要它們時,請小心關閉數據庫會話。否則,你會遇到內存泄漏或者 - 如果你有一個連接管理器 - 連接泄漏使你的應用程序掛起。 –

+0

我在想我最好把這個後端代碼分解成一個Data Acces層和一個Service層(不管怎樣,首先應該這樣做)。我將要讓AbstractDAO提供方法來請求一個事務 - 這又會獲取一個新的Session - 另一個關閉事務(這會關閉流程中的會話)。這將會話管理保留在DAO中。這會是一個合適的方式嗎? –

相關問題