HibernateDaoSupport 類session未關閉導致的串連泄露問題

來源:互聯網
上載者:User

Spring+Hibernate做項目, 發現有member在不加事務的情況下就去調用 getSession() 方法, 結果導致資料庫連接不能釋放, 也無法正常的提交事務(只能做查詢, 不能做save(), update()). 如果配合串連池使用的話, 不出幾分鐘就會導致串連池無法拿到新串連的情況.

不過, 只要給DAO或者Service加入了事務, 就不會出現串連泄漏的問題.

談談解決方案:

最佳方案: 加入事務, 例如 tx 標籤或者 @Transactional 都可以.

最笨方案: 修改代碼, 使用 HibernateTemplate 來完成相關操作:

public List queryAll( final String hql, final Object… args) {</p><p> List list = getHibernateTemplate().executeFind( new HibernateCallback() {</p><p> public Object doInHibernate(Session session)</p><p> throws HibernateException, SQLException {</p><p> Query query = session.createQuery(hql);</p><p> for ( int i =0; i < args. length ; i++) {</p><p> query.setParameter(i, args[i]);</p><p> }</p><p> List list = query.list();</p><p> return list;</p><p> }</p><p> });</p><p> return list; </p><p> }</p><p> public Serializable save(Object entity) {</p><p> return getHibernateTemplate().save(entity);</p><p> }<br />

但是缺陷顯而易見, 要有N多的代碼要進行改動.

HibernateDaoSupport 代碼裡面的原始說明文檔指出直接調用getSession()方法必須用配套的releaseSession(Session session)來釋放串連, 根據我的測試, 就算配置了 OpenSessionInViewFilter(前提: 不加事務), 也不會關閉這個Session. 也許有人說可以用串連池, 這種情況和Db pool沒關係, 用了pool就會發現串連很快就會滿, 只會over的更快.  反過來, 如果不配置OpenSessionInViewFilter, 在DAO裡提前用 releaseSession()關閉串連, 就可能會在JSP中出現Lazy載入異常. 另一個不配事務的問題就是無法更新或者插入資料. 下面就是原始的JavaDoc中的說明:

      /**</p><p> * Obtain a Hibernate Session, either from the current transaction or</p><p> * a new one. The latter is only allowed if the</p><p> * {@link org.springframework.orm.hibernate3.HibernateTemplate#setAllowCreate “allowCreate”}</p><p> * setting of this bean’s {@link #setHibernateTemplate HibernateTemplate} is “true”.</p><p> * <p><b> Note that this is not meant to be invoked from HibernateTemplate code</p><p> * but rather just in plain Hibernate code. </b> Either rely on a thread – bound</p><p> * Session or use it in combination with {@link #releaseSession} .</p><p> * <p> In general, it is recommended to use HibernateTemplate, either with</p><p> * the provided convenience operations or with a custom HibernateCallback</p><p> * that provides you with a Session to work on. HibernateTemplate will care</p><p> * for all resource management and for proper exception conversion.</p><p> * @return the Hibernate Session</p><p> * @throws DataAccessResourceFailureException if the Session couldn’t be created</p><p> * @throws IllegalStateException if no thread – bound Session found and allowCreate=false</p><p> * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)</p><p> */</p><p> protected final Session getSession()</p><p> throws DataAccessResourceFailureException, IllegalStateException {</p><p> return getSession( this . hibernateTemplate .isAllowCreate());</p><p> }</p><p> /**</p><p> * Close the given Hibernate Session, created via this DAO’s SessionFactory,</p><p> * if it isn’t bound to the thread (i.e. isn’t a transactional Session).</p><p> * <p> Typically used in plain Hibernate code, in combination with</p><p> * {@link #getSession} and {@link #convertHibernateAccessException} .</p><p> * @param session the Session to close</p><p> * @see org.springframework.orm.hibernate3.SessionFactoryUtils#releaseSession</p><p> */</p><p> protected final void releaseSession(Session session) {</p><p> SessionFactoryUtils.releaseSession(session, getSessionFactory());</p><p> }</p><p>

不需要改原始代碼的最終方案(方案三):

不過, 如果項目裡已經有了大量直接調用getSession()而且沒有加入事務配置的代碼(如曆史原因導致), 這些代碼太多, 沒法一一修改, 那就最好尋求其它方案, 最好是不需要修改原來的Java代碼的方案. 我採用的這第三個方案是重寫 HibernateDaoSupport用ThreadLocal儲存Session列表並編寫一個配套的過濾器來顯式關閉Session, 並在關閉之前嘗試提交事務. 下面是重寫的 HibernateDaoSupport 代碼:

 package closesessionfiter;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.List;import org.hibernate.HibernateException;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.springframework.dao.DataAccessException;import org.springframework.dao.DataAccessResourceFailureException;import org.springframework.dao.support.DaoSupport;import org.springframework.orm.hibernate3.HibernateTemplate;import org.springframework.orm.hibernate3.SessionFactoryUtils;/** * 修改後的避免串連泄漏的 HibernateDaoSupport, 多串連版本, 不保證跨DAO的事務. *  * @author   */public abstract class HibernateDaoSupport extends DaoSupport {/** 使用 ThreadLocal 儲存開啟的 Session 列表 */private static final ThreadLocal<List<Session>> sessions = new ThreadLocal<List<Session>>();/** * 擷取Hibernate串連. *  * @return */public static List<Session> getSessionList() {// 1. 先看看是否有了List get()List list = sessions.get();// 2. 沒有的話從建立一個, put()if (list == null) {list = new ArrayList();sessions.set(list);}// 3. 返回 Sessionreturn list;}/** * 關閉當前線程中未正常釋放的 Session. */public static void closeSessionList() {// 1. 先看看是否有了List get()List<Session> list = sessions.get();// 2. 有的話就直接關閉if (list != null) {System.out.println(SimpleDateFormat.getDateTimeInstance().format(new java.util.Date())+ " -------- 即將釋放未正常關閉的 Session");for (Session session : list) {System.out.println("正在關閉 session =" + session.hashCode());// ! 關閉前事務提交if (session.isOpen()) {try {session.getTransaction().commit();} catch (Exception ex) {try {session.getTransaction().rollback();} catch (HibernateException e) {// TODO Auto-generated catch block// e.printStackTrace();}}try {session.close();} catch (Exception ex) {}}// releaseSession(session); // 無法調用}sessions.remove();}}private HibernateTemplate hibernateTemplate;/** * Set the Hibernate SessionFactory to be used by this DAO. Will * automatically create a HibernateTemplate for the given SessionFactory. *  * @see #createHibernateTemplate * @see #setHibernateTemplate */public final void setSessionFactory(SessionFactory sessionFactory) {if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) {this.hibernateTemplate = createHibernateTemplate(sessionFactory);}}/** * Create a HibernateTemplate for the given SessionFactory. Only invoked if * populating the DAO with a SessionFactory reference! * <p> * Can be overridden in subclasses to provide a HibernateTemplate instance * with different configuration, or a custom HibernateTemplate subclass. *  * @param sessionFactory *            the Hibernate SessionFactory to create a HibernateTemplate for * @return the new HibernateTemplate instance * @see #setSessionFactory */protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {return new HibernateTemplate(sessionFactory);}/** * Return the Hibernate SessionFactory used by this DAO. */public final SessionFactory getSessionFactory() {return (this.hibernateTemplate != null ? this.hibernateTemplate.getSessionFactory() : null);}/** * Set the HibernateTemplate for this DAO explicitly, as an alternative to * specifying a SessionFactory. *  * @see #setSessionFactory */public final void setHibernateTemplate(HibernateTemplate hibernateTemplate) {this.hibernateTemplate = hibernateTemplate;}/** * Return the HibernateTemplate for this DAO, pre-initialized with the * SessionFactory or set explicitly. * <p> * <b>Note: The returned HibernateTemplate is a shared instance.</b> You * may introspect its configuration, but not modify the configuration (other * than from within an {@link #initDao} implementation). Consider creating a * custom HibernateTemplate instance via * <code>new HibernateTemplate(getSessionFactory())</code>, in which case * you're allowed to customize the settings on the resulting instance. */public final HibernateTemplate getHibernateTemplate() {return this.hibernateTemplate;}protected final void checkDaoConfig() {if (this.hibernateTemplate == null) {throw new IllegalArgumentException("'sessionFactory' or 'hibernateTemplate' is required");}}/** * Obtain a Hibernate Session, either from the current transaction or a new * one. The latter is only allowed if the * {@link org.springframework.orm.hibernate3.HibernateTemplate#setAllowCreate "allowCreate"} * setting of this bean's {@link #setHibernateTemplate HibernateTemplate} is * "true". * <p> * <b>Note that this is not meant to be invoked from HibernateTemplate code * but rather just in plain Hibernate code.</b> Either rely on a * thread-bound Session or use it in combination with * {@link #releaseSession}. * <p> * In general, it is recommended to use HibernateTemplate, either with the * provided convenience operations or with a custom HibernateCallback that * provides you with a Session to work on. HibernateTemplate will care for * all resource management and for proper exception conversion. *  * @return the Hibernate Session * @throws DataAccessResourceFailureException *             if the Session couldn't be created * @throws IllegalStateException *             if no thread-bound Session found and allowCreate=false * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, *      boolean) */protected final Session getSession() throws DataAccessResourceFailureException, IllegalStateException {Session session = getSession(this.hibernateTemplate.isAllowCreate());// 開始事務try {session.beginTransaction();} catch (HibernateException e) {e.printStackTrace();}getSessionList().add(session);return session;}/** * Obtain a Hibernate Session, either from the current transaction or a new * one. The latter is only allowed if "allowCreate" is true. * <p> * <b>Note that this is not meant to be invoked from HibernateTemplate code * but rather just in plain Hibernate code.</b> Either rely on a * thread-bound Session or use it in combination with * {@link #releaseSession}. * <p> * In general, it is recommended to use * {@link #getHibernateTemplate() HibernateTemplate}, either with the * provided convenience operations or with a custom * {@link org.springframework.orm.hibernate3.HibernateCallback} that * provides you with a Session to work on. HibernateTemplate will care for * all resource management and for proper exception conversion. *  * @param allowCreate *            if a non-transactional Session should be created when no *            transactional Session can be found for the current thread * @return the Hibernate Session * @throws DataAccessResourceFailureException *             if the Session couldn't be created * @throws IllegalStateException *             if no thread-bound Session found and allowCreate=false * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, *      boolean) */protected final Session getSession(boolean allowCreate) throws DataAccessResourceFailureException,IllegalStateException {return (!allowCreate ? SessionFactoryUtils.getSession(getSessionFactory(), false) : SessionFactoryUtils.getSession(getSessionFactory(), this.hibernateTemplate.getEntityInterceptor(), this.hibernateTemplate.getJdbcExceptionTranslator()));}/** * Convert the given HibernateException to an appropriate exception from the * <code>org.springframework.dao</code> hierarchy. Will automatically * detect wrapped SQLExceptions and convert them accordingly. * <p> * Delegates to the * {@link org.springframework.orm.hibernate3.HibernateTemplate#convertHibernateAccessException} * method of this DAO's HibernateTemplate. * <p> * Typically used in plain Hibernate code, in combination with * {@link #getSession} and {@link #releaseSession}. *  * @param ex *            HibernateException that occured * @return the corresponding DataAccessException instance * @see org.springframework.orm.hibernate3.SessionFactoryUtils#convertHibernateAccessException */protected final DataAccessException convertHibernateAccessException(HibernateException ex) {return this.hibernateTemplate.convertHibernateAccessException(ex);}/** * Close the given Hibernate Session, created via this DAO's SessionFactory, * if it isn't bound to the thread (i.e. isn't a transactional Session). * <p> * Typically used in plain Hibernate code, in combination with * {@link #getSession} and {@link #convertHibernateAccessException}. *  * @param session *            the Session to close * @see org.springframework.orm.hibernate3.SessionFactoryUtils#releaseSession */protected final void releaseSession(Session session) {SessionFactoryUtils.releaseSession(session, getSessionFactory());}}

用這個類來覆蓋Spring內建的那個HibernateDaoSupport, 然後隨便編寫一個過濾器, 如下所示:

import java.io.IOException;<br />import javax.servlet.Filter;<br />import javax.servlet.FilterChain;<br />import javax.servlet.FilterConfig;<br />import javax.servlet.ServletException;<br />import javax.servlet.ServletRequest;<br />import javax.servlet.ServletResponse;</p><p>/**<br /> * @author sutk.<br /> * hibernate查詢時,session不會自動關閉.<br /> * 此過濾器用來關閉session.<br /> */<br />public class CloseSessionFilter implements Filter {</p><p>public void destroy() {</p><p>}</p><p>public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,<br />ServletException {<br /> chain.doFilter(req, res);<br /> HibernateDaoSupport.closeSessionList();<br />}</p><p>public void init(FilterConfig arg0) throws ServletException {</p><p>}<br />}

 把這個過濾器配置在過濾器鏈的第一個, 就OK了.
 <!-- session關閉過濾器 --><filter><filter-name>sessionCloseFilter</filter-name><filter-class>closesessionfiter.CloseSessionFilter</filter-class></filter><filter-mapping><filter-name>sessionCloseFilter</filter-name><url-pattern>*.do</url-pattern></filter-mapping>

最後也許會有人說, 為什麼不用tx標籤在Spring中來配置一個萬用字元就全部加入了事務了呢? 不過很遺憾, 經測試發現此方式無法實現跨DAO的Hibernate事務, 所以只好很無奈的放棄了這種方式.

 

配置交易管理:http://www.blogjava.net/robbie/archive/2009/04/05/264003.html

原帖:http://yhkyo.com/archives/196

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.