2011-05-29 87 views
3

我在我的項目開始。所以我試圖設計一個避免Hibernate LazyInitializationExceptions的架構。到目前爲止,我的applicationContext.xml有:架構,以避免Hibernate LazyInitializationExceptions

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="configLocation"> 
     <value>/WEB-INF/hibernate.cfg.xml</value> 
    </property> 
    <property name="configurationClass"> 
     <value>org.hibernate.cfg.AnnotationConfiguration</value> 
    </property>   
    <property name="hibernateProperties"> 
     <props> 
      <prop key="hibernate.dialect">${hibernate.dialect}</prop>   
      <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>  
     </props> 
    </property> 
    <property name="eventListeners"> 
     <map> 
      <entry key="merge"> 
       <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/> 
      </entry> 
     </map> 
    </property> 
</bean> 

<bean id="dao" class="info.ems.hibernate.HibernateEMSDao" init-method="createSchema"> 
    <property name="hibernateTemplate"> 
     <bean class="org.springframework.orm.hibernate3.HibernateTemplate"> 
      <property name="sessionFactory" ref="sessionFactory"/> 
      <property name="flushMode"> 
       <bean id="org.springframework.orm.hibernate3.HibernateAccessor.FLUSH_COMMIT" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>      
      </property> 
     </bean> 
    </property>   
    <property name="schemaHelper"> 
     <bean class="info.ems.hibernate.SchemaHelper">         
      <property name="driverClassName" value="${database.driver}"/> 
      <property name="url" value="${database.url}"/> 
      <property name="username" value="${database.username}"/> 
      <property name="password" value="${database.password}"/> 
      <property name="hibernateDialect" value="${hibernate.dialect}"/> 
      <property name="dataSourceJndiName" value="${database.datasource.jndiname}"/> 
     </bean>     
    </property> 
</bean>  

的hibernate.cfg.xml中:

<hibernate-configuration> 
    <session-factory>  
     <mapping class="info.ems.models.User" /> 
     <mapping class="info.ems.models.Role" /> 
    </session-factory> 
</hibernate-configuration> 

的Role.java:

@Entity 
@Table(name="ROLE") 
@Access(AccessType.FIELD) 
public class Role implements Serializable { 

    private static final long serialVersionUID = 3L; 

    @Id 
    @Column(name="ROLE_ID", updatable=false, nullable=false) 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id; 

    @Column(name="USERNAME") 
    private String username; 

    @Column(name="ROLE") 
    private String role; 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    public String getUsername() { 
     return username; 
    } 

    public void setUsername(String username) { 
     this.username = username; 
    } 

    public String getRole() { 
     return role; 
    } 

    public void setRole(String role) { 
     this.role = role; 
    } 
} 

而且User.java:

@Entity 
@Table(name = "USER") 
@Access(AccessType.FIELD) 
public class User implements UserDetails, Serializable { 

    private static final long serialVersionUID = 2L; 

    @Id 
    @Column(name = "USER_ID", updatable=false, nullable=false) 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id; 

    @Column(name = "USERNAME") 
    private String username; 

    @Column(name = "PASSWORD") 
    private String password; 

    @Column(name = "NAME") 
    private String name; 

    @Column(name = "EMAIL") 
    private String email; 

    @Column(name = "LOCKED") 
    private boolean locked; 

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = Role.class) 
    @JoinTable(name = "USER_ROLE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") }) 
    private Set<Role> roles; 

    @Override 
    public GrantedAuthority[] getAuthorities() { 
     List<GrantedAuthorityImpl> list = new ArrayList<GrantedAuthorityImpl>(0); 
     for (Role role : roles) { 
      list.add(new GrantedAuthorityImpl(role.getRole())); 
     } 
     return (GrantedAuthority[]) list.toArray(new GrantedAuthority[list.size()]); 
    } 

    @Override 
    public boolean isAccountNonExpired() { 
     return true; 
    } 

    @Override 
    public boolean isAccountNonLocked() { 
     return !isLocked(); 
    } 

    @Override 
    public boolean isCredentialsNonExpired() { 
     return true; 
    } 

    @Override 
    public boolean isEnabled() { 
     return true; 
    } 

    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    @Override 
    public String getUsername() { 
     return username; 
    } 

    public void setUsername(String username) { 
     this.username = username; 
    } 

    @Override 
    public String getPassword() { 
     return password; 
    } 

    public void setPassword(String password) { 
     this.password = password; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getEmail() { 
     return email; 
    } 

    public void setEmail(String email) { 
     this.email = email; 
    } 

    public boolean isLocked() { 
     return locked; 
    } 

    public void setLocked(boolean locked) { 
     this.locked = locked; 
    } 

    public Set<Role> getRoles() { 
     return roles; 
    } 

    public void setRoles(Set<Role> roles) { 
     this.roles = roles; 
    } 
} 

HibernateEMSDao有兩種保存方法和lo從數據庫ading用戶:

public void saveUser(final User user) {  
    getHibernateTemplate().execute(new HibernateCallback() { 

     @Override 
     public Object doInHibernate(Session session) throws HibernateException, SQLException { 
      session.flush(); 
      session.setCacheMode(CacheMode.IGNORE); 
      session.save(user); 
      session.flush(); 
      return null; 
     } 
    });  
} 

public User getUser(final Long id) { 
    return (User) getHibernateTemplate().execute(new HibernateCallback() { 

     @Override 
     public Object doInHibernate(Session session) throws HibernateException, SQLException { 
      return session.get(User.class, id); 
     } 
    }); 
} 

現在我測試了一下,如果我實現HibernateEMSDao#getUser爲:

public User getUser(final Long id) { 
    getHibernateTemplate().load(User.class, id);   
} 

我越來越LazyInitializationExcaption - 會話關閉。但第一種方式工作正常。所以我需要建議在不久的將來避免這種例外。任何小的信息都是可觀的。

感謝和問候。

注意:重新啓動服務器後出現錯誤。

編輯:程式碼:

public void saveUser(final User user) {  
    Session session = getSession(); 
    Transaction transaction = session.beginTransaction(); 
    session.save(user); 
    transaction.commit(); 
    session.close(); 
} 
public User getUser(final Long id) { 
    Session session = getSession(); 
    session.enableFetchProfile("USER-ROLE-PROFILE"); 
    User user = (User) session.get(User.class, id); 
    session.disableFetchProfile("USER-ROLE-PROFILE"); 
    session.close(); 
    return user; 
} 

回答

2

saveUser不應該刷新會話。沖洗會議應該是非常罕見的。讓Hibernate照顧這一點,並且你的應用程序將更加高​​效。

在這樣的地方設置緩存模式也是非常奇怪的。你爲什麼這樣做?

至於說明爲什麼在使用load時出現異常,而不是在使用get時出現這種情況:這是因爲負載假定您知道該實體存在。而不是執行選擇查詢以從數據庫中獲取用戶數據,它只是返回一個代理,該代理將在對象首次調用方法時獲取數據。如果會話在第一次調用方法時關閉,Hibernate不能再獲取數據並拋出異常。 load應該很少使用,除非啓動與現有對象的某種關係而不必獲取其數據。在其他情況下使用get

我避免LazyInitializationException中一般的策略是:

  • 使用附加的對象只要有可能。該圖由方法加載
  • 返回文檔分離對象,和單元測試,該圖被確實裝入
  • 喜歡merge超過upadatesaveOrUpdate。這些方法可以留下附有一些對象的對象的圖形,而其他對象則根據級聯而分離。 merge不會遇到這個問題。
+0

「讓Hibernate照顧好這一點,你的應用程序將會更有效率。」我是否應該爲此做特殊設置? – 2011-05-29 15:28:49

+0

我在互聯網上找到了一個例子,我看到這種類型的操作像第一個session.flush(),然後是緩存模式操作,最後再次flush()。 – 2011-05-29 15:30:48

+0

不要相信你在互聯網上找到的所有東西。閱讀參考手冊:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/。當需要刷新時,默認的刷新模式會自動刷新。 – 2011-05-29 17:04:05

4

與延遲加載處理是持續的挑戰與Hibernate,JPA或奧姆斯在一般工作時。

這不僅是爲了防止發生LazyInitializationException,而且還關於高效地執行查詢。即使使用通用的DAO,策略也應該儘可能多地獲取您真正需要的數據。

來自Mike Keith by Apress的書Pro JPA 2專門介紹了這方面的內容,但似乎並沒有一個通用的解決方案總是有效。

有時它可以幫助做FETCH連接。這確實意味着你不使用實體管理器的find方法,但是使用JPQL(或HQL,如果這是你的毒藥)查詢所有內容。您的DAO可以包含幾種不同的方法,以這種方式將實體圖形提升到各種級別。數據通常以這種方式相當高效地獲取,但是在很多情況下,您可能會獲取太多數據。

Mike Keith建議的另一種解決方案是利用extended persistence context。在這種情況下,上下文(Hibernate會話)未綁定到事務,但保持打開狀態。因此實體保持連接狀態,延遲加載按預期工作。

儘管你必須確保最終關閉擴展的上下文。這樣做的一種方式是由一個有約束的有狀態會話bean來管理,例如請求範圍或對話範圍。這樣,這個bean就會在這個範圍的末尾自動銷燬,並且這個輪到它自動關閉上下文。然而,它並不是沒有它自己的問題。一個開放的上下文將繼續消耗內存並使其保持更長時間(通常比請求範圍更長)可能會導致內存不足的嚴重風險。如果你知道你只與少數實體打交道沒關係,但你必須在這裏小心。

取決於延遲加載的另一個問題是衆所周知的1 + N查詢問題。遍歷中等大小的結果列表可能會導致數百或數千個查詢被髮送到數據庫。我想我不必解釋這可以完全破壞你的表現。

這個1 + N查詢問題有時可以通過大量依賴二級緩存來解決。如果實體數量不是那麼大,並且如果它們沒有經常更新,確保它們全部被緩存(使用Hibernate或JPA的二級實體緩存)可以大大減少這個問題。但是......這是兩個大的「如果」。如果您的主要實體只引用一個未緩存的實體,您將再次獲得數百個查詢。

另一種方法是利用Hibernate中的fetch profile支持,該支持可以部分與其他方法結合使用。參考手冊在此處有一節:http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

因此,對於您的問題似乎沒有單一明確的答案,但只有很多想法和實踐都高度依賴於您的個人情況。