2010-06-14 104 views
38

的答案向下滾動到本月底...休眠/春:無法初始化懶洋洋 - 沒有會話或會話關閉

最根本的問題是一樣的問多時間。我有一個簡單的程序,包含兩個POJO事件和用戶 - 用戶可以有多個事件。

@Entity 
@Table 
public class Event { 
private Long id; 
private String name; 
private User user; 

@Column 
@Id 
@GeneratedValue 
public Long getId() {return id;} 
public void setId(Long id) { this.id = id; } 

@Column 
public String getName() {return name;} 
public void setName(String name) {this.name = name;} 

@ManyToOne 
@JoinColumn(name="user_id") 
public User getUser() {return user;} 
public void setUser(User user) {this.user = user;} 

} 

用戶:

@Entity 
@Table 
public class User { 
private Long id; 
private String name; 
private List<Event> events; 

@Column 
@Id 
@GeneratedValue 
public Long getId() { return id; } 
public void setId(Long id) { this.id = id; } 

@Column 
public String getName() { return name; } 
public void setName(String name) { this.name = name; } 

@OneToMany(mappedBy="user", fetch=FetchType.LAZY) 
public List<Event> getEvents() { return events; } 
public void setEvents(List<Event> events) { this.events = events; } 

} 

注:這是一個示例項目。我真的想要在這裏使用懶惰提取。

現在我們需要配置Spring和Hibernate和具有裝載一個簡單的基本-db.xml:

 

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:aop="http://www.springframework.org/schema/aop" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
      http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 


<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close" scope="thread"> 
    <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
    <property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" /> 
    <property name="username" value="root" /> 
    <property name="password" value="" /> 
    <aop:scoped-proxy/> 
</bean> 

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> 
    <property name="scopes"> 
    <map> 
    <entry key="thread"> 
    <bean class="org.springframework.context.support.SimpleThreadScope" /> 
    </entry> 
    </map> 
    </property> 
</bean> 

<bean id="mySessionFactory" 
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread"> 
    <property name="dataSource" ref="myDataSource" /> 
    <property name="annotatedClasses"> 
    <list> 
    <value>data.model.User</value> 
    <value>data.model.Event</value> 
    </list> 
    </property> 
    <property name="hibernateProperties"> 
    <props> 
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> 
    <prop key="hibernate.show_sql">true</prop> 
    <prop key="hibernate.hbm2ddl.auto">create</prop> 
    </props> 
    </property> 
    <aop:scoped-proxy/> 

</bean> 

<bean id="myUserDAO" class="data.dao.impl.UserDaoImpl"> 
    <property name="sessionFactory" ref="mySessionFactory" /> 
</bean> 

<bean id="myEventDAO" class="data.dao.impl.EventDaoImpl"> 
    <property name="sessionFactory" ref="mySessionFactory" /> 
</bean> 

</beans> 
 

注:我與CustomScopeConfigurer和SimpleThreadScope發揮各地,但沒有改變任何東西。

我有一個簡單的DAO-implement執行(僅粘貼userDAO的 - 的EventDao幾乎是相同的 - 除非出了「listWith」功能:

 

public class UserDaoImpl implements UserDao{ 

private HibernateTemplate hibernateTemplate; 

public void setSessionFactory(SessionFactory sessionFactory) { 
    this.hibernateTemplate = new HibernateTemplate(sessionFactory); 

} 

@SuppressWarnings("unchecked") 
@Override 
public List listUser() { 
    return hibernateTemplate.find("from User"); 
} 

@Override 
public void saveUser(User user) { 
    hibernateTemplate.saveOrUpdate(user); 

} 

@Override 
public List listUserWithEvent() { 

    List users = hibernateTemplate.find("from User"); 
    for (User user : users) { 
    System.out.println("LIST : " + user.getName() + ":"); 
    user.getEvents().size(); 
    } 
    return users; 
} 

} 
 

我正在org.hibernate.LazyInitializationException - 未能懶洋洋地初始化角色的集合:data.model.User.events,沒有會話或會話關閉在符合user.getEvents()大小();

最後但並非最不重要的位置。是我使用的測試類:

 

public class HibernateTest { 

public static void main(String[] args) { 

    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); 


    UserDao udao = (UserDao) ac.getBean("myUserDAO"); 
    EventDao edao = (EventDao) ac.getBean("myEventDAO"); 


    System.out.println("New user..."); 
    User user = new User(); 
    user.setName("test"); 

    Event event1 = new Event(); 
    event1.setName("Birthday1"); 
    event1.setUser(user); 

    Event event2 = new Event(); 
    event2.setName("Birthday2"); 
    event2.setUser(user); 

    udao.saveUser(user); 
    edao.saveEvent(event1); 
    edao.saveEvent(event2); 

    List users = udao.listUserWithEvent(); 
    System.out.println("Events for users"); 
    for (User u : users) { 

    System.out.println(u.getId() + ":" + u.getName() + " --"); 
    for (Event e : u.getEvents()) 
    { 
    System.out.println("\t" + e.getId() + ":" + e.getName()); 
    } 
    } 

    ((ConfigurableApplicationContext)ac).close(); 
} 

} 
 

這裏是例外:

 
1621 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed 
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed 
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) 
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) 
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) 
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) 
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) 
at HibernateTest.main(HibernateTest.java:44) 
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed 
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) 
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) 
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) 
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) 
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) 
at HibernateTest.main(HibernateTest.java:44) 

事情試過,但沒有奏效:

  • 分配threadScope和使用的beanfactory(我用 「要求」 或 「線」 - 沒有什麼區別注意到):
 
    // scope stuff 
    Scope threadScope = new SimpleThreadScope(); 
    ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); 
    beanFactory.registerScope("request", threadScope); 
    ac.refresh(); 
... 
 
... 
    Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction(); 
    tx.begin(); 
    users = udao.listUserWithEvent(); 
... 
  • 的listUserWithEvent(內獲得一個交易)
 
public List listUserWithEvent() { 
    SessionFactory sf = hibernateTemplate.getSessionFactory(); 
    Session s = sf.openSession(); 
    Transaction tx = s.beginTransaction(); 
    tx.begin(); 

    List users = hibernateTemplate.find("from User"); 
    for (User user : users) { 
    System.out.println("LIST : " + user.getName() + ":"); 
    user.getEvents().size(); 
    } 
    tx.commit(); 
    return users; 
} 

我真是的想法現在:從迪奧得到會話對象建立一個交易。此外,使用listUser或listEvent只是工作正常。

向前邁進了一步:

感謝蒂埃裏我一步(我認爲)。我創建了MyTransaction類,並在那裏完成了我的全部工作,從春季開始獲取所有內容。新的主看起來像這樣:

 

public static void main(String[] args) { 

    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); 

    // getting dao 
    UserDao udao = (UserDao) ac.getBean("myUserDAO"); 
    EventDao edao = (EventDao) ac.getBean("myEventDAO"); 

    // gettting transaction template 
    TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate"); 

    MyTransaction mt = new MyTransaction(udao, edao); 
    transactionTemplate.execute(mt); 

    ((ConfigurableApplicationContext)ac).close(); 
} 
 

不幸的是,現在有一個空指針異常@:user.getEvents()大小(); (在daoImpl中)。

我知道它不應該是null(既不是從控制檯的輸出,也不是從數據庫佈局)。

下面是詳細信息控制檯輸出(我做了user.getEvent()檢查== null,並且印有「event爲null」):

 
New user... 
Hibernate: insert into User (name) values (?) 
Hibernate: insert into User (name) values (?) 
Hibernate: insert into Event (name, user_id) values (?, ?) 
Hibernate: insert into Event (name, user_id) values (?, ?) 
Hibernate: insert into Event (name, user_id) values (?, ?) 
List users: 
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ 
1:User1 
2:User2 
List events: 
Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_ 
1:Birthday1 for 1:User1 
2:Birthday2 for 1:User1 
3:Wedding for 2:User2 
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ 
Events for users 
1:User1 -- 
EVENT is NULL 
2:User2 -- 
EVENT is NULL 

您可以從http://www.gargan.org/code/hibernate-test1.tgz獲得示例項目(這是一個Eclipse/Maven項目)

解決方案(控制檯應用程序)

實際上有兩個方案來解決這個問題 - 根據您的環境:

對於需要捕捉的actutal DB邏輯,並採取交易的保健事務模板控制檯應用程序通過調用

 

public class UserGetTransaction implements TransactionCallback{ 

public List users; 

protected ApplicationContext context; 

public UserGetTransaction (ApplicationContext context) { 
    this.context = context; 
} 

@Override 
public Boolean doInTransaction(TransactionStatus arg0) { 
    UserDao udao = (UserDao) ac.getBean("myUserDAO"); 
    users = udao.listUserWithEvent(); 
    return null; 
} 

} 
 

您可以使用此:

 

TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate"); 
UserGetTransaction mt = new UserGetTransaction(context); 
transactionTemplate.execute(mt); 
 

爲了這個工作你需要爲spring定義模板類(即。在你的基本-db.xml):

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 
    <property name="transactionManager" ref="transactionManager"/> 
</bean> 

另一個(可能的)解決方案

感謝安迪

PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager"); 
    DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED); 

transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); 
    TransactionStatus status = transactionManager.getTransaction(transactionAttribute); 
    boolean success = false; 
    try { 
     new UserDataAccessCode().execute(); 
     success = true; 
    } finally { 
     if (success) { 
     transactionManager.commit(status); 
     } else { 
     transactionManager.rollback(status); 
     } 
    } 

解決方案(對servlet)

Servlet是沒有那麼大的問題。當你有一個servlet,你可以簡單地啓動和事務在您的函數的開始結合,並在結束時再次解除綁定:

public void doGet(...) { 
    SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory"); 
    Session session = SessionFactoryUtils.getSession(sessionFactory, true); 
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); 

// Your code.... 

    TransactionSynchronizationManager.unbindResource(sessionFactory); 
} 

回答

26

我想你不應該使用Hibernate Session事務方法,而是讓春天做那。

添加到您的春天的conf:

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 
    <property name="sessionFactory" ref="mySessionFactory" /> 
</bean> 

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 
    <property name="transactionManager" ref="txManager"/> 
</bean> 

,然後我將修改使用Spring的事務模板測試方法:

public static void main(String[] args) { 
    // init here (getting dao and transaction template) 

    transactionTemplate.execute(new TransactionCallback() { 
     @Override 
     public Object doInTransaction(TransactionStatus status) { 
      // do your hibernate stuff in here : call save, list method, etc 
     } 
    } 
} 

一個側面說明,@OneToMany協會是懶惰默認情況下,所以你不需要懶惰地註釋它。(@ *一對多默認情況下懶,@ * TOONE默認情況下EAGER)

編輯:這是現在所但從休眠點發生的事情:

  • 打開的會話(與交易開始)
  • 保存用戶,並保持它的會話(見會話緩存作爲一個實體的HashMap,其中關鍵是實體ID)
  • 保存一個事件,並保持它在會議
  • 拯救他人的事件,並把它放在會議
  • ...同樣與所有的保存操作...

  • 然後加載所有用戶(下稱「從用戶」查詢)

  • 在這一點休眠看到,它已經在對象的會議上,所以丟棄它從請求中獲得的一個,並從會話中返回一個。
  • 您的用戶在會話中沒有初始化其事件集合,因此您會得到空值。
  • ...

這裏有幾點,以提高你的代碼:

    在模型中,不需要收集訂貨時
  • ,使用設置,沒有列出您的收藏(私人設置事件,而不是私人列表事件)在模型
  • ,鍵入您的收藏集,否則Hibernate不會給取的實體(私人設置<事件>事件)
  • 當你設置一個bidire的一面ctional關係,並且您希望在同一個事務中使用關係的mappedBy方,設置雙方。 Hibernate在下一個tx之前不會爲你做(當會話是來自db狀態的全新視圖時)。

因此,要解決點以上,要麼做一個事務中保存,而在另外一個裝載:

public static void main(String[] args) { 
    // init here (getting dao and transaction template) 
    transactionTemplate.execute(new TransactionCallback() { 
     @Override 
     public Object doInTransaction(TransactionStatus status) { 
      // save here 
     } 
    } 

    transactionTemplate.execute(new TransactionCallback() { 
     @Override 
     public Object doInTransaction(TransactionStatus status) { 
      // list here 
     } 
    } 
} 

或設置兩面:

... 
event1.setUser(user); 
... 
event2.setUser(user); 
... 
user.setEvents(Arrays.asList(event1,event2)); 
... 

(也可做不要忘了解決上面的代碼增強點,設置不列表,集合鍵入)

+1

我試過你的解決方案,我擺脫了惰性問題 - 不幸的是,它不會加載任何事件:user.getEvents()產生一個空指針(儘管我可以在數據庫中看到並從第一個迭代,用戶確實有事件關聯)。 hibernateTemplate.find是否有可能解決依賴問題? – Niko 2010-06-15 06:56:05

+0

我上傳了我的測試項目,並附上了您的建議。也許你可以找出發生了什麼事情。 – Niko 2010-06-15 07:10:03

3

問題是你的dao使用一個hibernate會話,但是l user.getName(我假設它是拋出的地方)的負載在該會話之外發生 - 或者根本不在會話中,或者在另一個會話中。通常,我們在之前打開一個休眠會話,我們進行DAO調用,直到完成所有延遲加載後才關閉它。 Web請求通常包含在一個大會話中,所以這些問題不會發生。

通常我們已經將我們的dao和懶惰調用包裝在SessionWrapper中。像下面這樣:

public class SessionWrapper { 
    private SessionFactory sessionFactory; 
    public void setSessionFactory(SessionFactory sessionFactory) { 
     this.hibernateTemplate = new HibernateTemplate(sessionFactory); 
    } 
    public <T> T runLogic(Callable<T> logic) throws Exception { 
     Session session = null; 
     // if the session factory is already registered, don't do it again 
     if (TransactionSynchronizationManager.getResource(sessionFactory) == null) { 
      session = SessionFactoryUtils.getSession(sessionFactory, true); 
      TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); 
     } 

     try { 
      return logic.call(); 
     } finally { 
      // if we didn't create the session don't unregister/release it 
      if (session != null) { 
       TransactionSynchronizationManager.unbindResource(sessionFactory); 
       SessionFactoryUtils.releaseSession(session, sessionFactory); 
      } 
     } 
    } 
} 

顯然SessionFactory的是被注入到你的DAO一樣的SessionFactory。


在你的情況下,你應該在這個邏輯中包裝整個listUserWithEvent主體。例如:

public List listUserWithEvent() { 
    return sessionWrapper.runLogic(new Callable<List>() { 
     public List call() { 
      List users = hibernateTemplate.find("from User"); 
      for (User user : users) { 
       System.out.println("LIST : " + user.getName() + ":"); 
       user.getEvents().size(); 
      } 
     } 
    }); 
} 

您需要將SessionWrapper實例注入到您的daos中。

+0

不幸的是我並沒有真正把這個工作。你的意思是你DAOImpl擴展了SessionWrapper嗎?誰(在我的例子中)會調用runLogic,Callable是什麼?在擴展示例並使其適應我提供的示例代碼時,也許它是清理者。 此外,我的問題是特定於非網絡的,因爲我想使用與Web相同的模型/道具,也適用於獨立應用程序(我們的數據搜尋器)。 – Niko 2010-06-15 07:00:20

+0

對不起,它令人困惑。我已經添加了一個RunLogic示例的部分。你根本不需要改變你的道。它應該注入與SessionWrapper注入相同的SessionFactory。 SessionWrapper然後被注入到daos中。 – Gray 2010-06-15 12:35:39

+0

這就是春季交易模板正在做的事情。 (它只是缺少輸入:/) – Thierry 2010-06-15 12:42:06

7

我在這裏尋找關於類似問題的提示。我嘗試了Thierry提到的解決方案,但沒有奏效。從那以後,我嘗試了這些線和它的工作:

SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory"); 
Session session = SessionFactoryUtils.getSession(sessionFactory, true); 
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); 

事實上我在做什麼是批過程必須利用Spring existings經理/服務。加載上下文並進行一些調用後,我創建了一個着名的問題「未能懶惰地初始化一個集合」。這3條線爲我解決了它。

+0

修正了它!雖然我的XML文件說所以我必須做getBean(「wfSessionFactory」)。也可能在結尾處使用「unbindResource」調用(請參閱問題末尾的答案) – rogerdpack 2011-07-15 22:52:44

9

在Web應用程序的情況下,也可以在web.xml中聲明一個特殊的過濾器,會做的session-per-要求:

<filter> 
    <filter-name>openSessionInViewFilter</filter-name> 
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> 
</filter> 

<filter-mapping> 
    <filter-name>openSessionInViewFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

之後,你可以在任何時候lazyload數據請求。

+0

在我的應用程序中,此過濾器也被聲明,但異常是拋出。 – 2013-08-08 09:12:15

2

有趣!

我在@控制器的@RequestMapping處理方法同樣的問題。 簡單的解決方案是一個@Transactional註釋添加到處理程序方法,從而使會話保持打開的方法體執行的整個過程中

1

最簡單的方法來實現:

內的範圍會議[與@Transactional註釋的API裏面],請執行以下操作:

如果A有一個列表<乙>被延遲加載,只需撥打一個API,它可以確保列表加載

這是什麼API?

尺寸(); List類的API。

因此,所有需要的只是:

Logger.log(a.getBList.size());

這個簡單的日誌大小的呼叫確保它在計算列表的大小之前得到整個列表。現在你不會得到異常!

+2

這不適合我。 – abbas 2013-08-01 09:54:26

0

什麼JBoss中爲我們工作是解決#2從this site at Java Code Geeks拍攝。

web.xml中:

<filter> 
     <filter-name>ConnectionFilter</filter-name> 
     <filter-class>web.ConnectionFilter</filter-class> 
    </filter> 
    <filter-mapping> 
     <filter-name>ConnectionFilter</filter-name> 
     <url-pattern>/faces/*</url-pattern> 
    </filter-mapping> 

ConnectionFilter:

import java.io.IOException; 
import javax.annotation.Resource; 
import javax.servlet.*; 
import javax.transaction.UserTransaction; 

public class ConnectionFilter implements Filter { 
    @Override 
    public void destroy() { } 

    @Resource 
    private UserTransaction utx; 

    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
     try { 
      utx.begin(); 
      chain.doFilter(request, response); 
      utx.commit(); 
     } catch (Exception e) { } 
    } 

    @Override 
    public void init(FilterConfig arg0) throws ServletException { } 
} 

也許這將與Spring工作了。

相關問題