隨筆-88  評論-77  文章-48  trackbacks-0

          Hibernate內(nèi)嵌了對C3P0,Proxool,JNDI數(shù)據(jù)源等數(shù)據(jù)庫連接池的支持。但當(dāng)我們需要使用除了這幾個(gè)數(shù)據(jù)源外的其他數(shù)據(jù)源的時(shí)候就有問題了,例如我們需要用Apache的開源連接池項(xiàng)目DBCP,或者說我們想要使用多數(shù)JDBC驅(qū)動(dòng)程序中自帶的XxxxDataSource時(shí),Hibernate就沒有提供對這方面的支持。慶幸的是Hibernate做為一個(gè)強(qiáng)大的數(shù)據(jù)持久層組件,它在實(shí)現(xiàn)數(shù)據(jù)庫連接方面的擴(kuò)展性也是非常強(qiáng)大的。本文將介紹兩種如何在Hibernate項(xiàng)目中使用自定義數(shù)據(jù)源的方法。

          本文假設(shè)你已經(jīng)有Hibernate的開發(fā)經(jīng)驗(yàn)。

          在開始之前應(yīng)該先明確你的項(xiàng)目中的具體情況,也就是確認(rèn)Hibernate內(nèi)嵌的組件是否真的無法支持你的應(yīng)用需要。例如C3P0或者Proxool已經(jīng)可以滿足大部分?jǐn)?shù)據(jù)庫的需要,又或者你的數(shù)據(jù)源是在應(yīng)用服務(wù)器中配置的,那么你也沒有必要進(jìn)行擴(kuò)展,你可以直接使用DatasourceConnectionProvider來讓Hibernate使用你所定義的數(shù)據(jù)源。

          那么什么時(shí)候你需要擴(kuò)展Hibernate對數(shù)據(jù)源的支持呢?可能你永遠(yuǎn)也用不上,但我一直在用。我用的原因可能不能成為正當(dāng)?shù)睦碛桑驗(yàn)镃3P0或者Proxool總有些小地方的不足讓我不爽,個(gè)人更偏向于DBCP連接池。或許本文應(yīng)該改名為《讓Hibernate支持DBCP數(shù)據(jù)源》,其實(shí)DBCP只不過是我的一個(gè)具體的例子,本文具有更普遍的應(yīng)用意義。下面我們具體介紹兩種不同的擴(kuò)展思路。

          思路一:使用外部定義數(shù)據(jù)源

          假設(shè)我們已經(jīng)有了一個(gè)WEB項(xiàng)目,該項(xiàng)目采用了Struts框架,而且我們已經(jīng)在Struts中配置了數(shù)據(jù)源,也有不少的代碼是依賴這個(gè)數(shù)據(jù)源運(yùn)行的。現(xiàn)在我們需要給項(xiàng)目中加入對Hibernate的支持,但又不想去修改舊的已經(jīng)成功穩(wěn)定運(yùn)行的代碼了。那我們該怎么辦?如果同樣在Hibernate配置一個(gè)數(shù)據(jù)源指到同一個(gè)數(shù)據(jù)庫,相信你也不樂意這樣干,因?yàn)橐坏┡渲蒙嫌行薷哪敲碨truts和Hibernate的配置都需要修改,這個(gè)也只是麻煩一點(diǎn)而已,最要命的是沒法讓原有的代碼和Hibernate共用一個(gè)數(shù)據(jù)庫連接,因此事務(wù)處理也就無從談起。

          說那么多理由,無非就是為了讓Hibernate可以使用Struts中配置的數(shù)據(jù)源,而我們暫且不去考慮這是否是最好的解決方法。

          在Hibernate中有一個(gè)UserSuppliedConnectionProvider類,其實(shí)這個(gè)類什么也不干,你一旦讓它干點(diǎn)啥吧,它還凈出異常,搞得你很是惱火。在Hibernate中,這個(gè)類的含義是要求用戶自己來提供數(shù)據(jù)庫連接的獲取方法,同時(shí)當(dāng)然也要自己負(fù)責(zé)關(guān)閉連接。

          為了使用Struts中配置的數(shù)據(jù)源,我們就不能直接調(diào)用SessionFactory.openSession()方法來獲取Session實(shí)例,因?yàn)槟闳绻麤]有在Hibernate中配置任何的數(shù)據(jù)庫連接,那Hibernate會默認(rèn)讓UserSuppliedConnectionProvider類來跟你搗亂,你會收到很多異常信息,反復(fù)提醒我們必須自己提供數(shù)據(jù)庫連接!我們要做還是調(diào)用openSession方法,不同的是需要先從Struts的數(shù)據(jù)源中獲取數(shù)據(jù)庫連接,然后傳遞該連接給openSession方法(參照 SessionFactory.openSession(Connection) 方法)。

          下面是我寫的一個(gè)代碼片斷



          														
          																//獲取Session實(shí)例
          public Session getSession(){
          	ServletContext contxt = ....
          	SessionFactory sessions = ....
          
          	DataSource ds = (DataSource)context.getAttribute(Globals.DATA_SOURCE_KEY);
          	final Connection conn = ds.getConnection();
          	return sessions.openSession(conn);
          }
          
          //釋放Session
          public void closeSession(Session ssn){
          	ssn.connection().close();
          	ssn.close();
          }
          
          														
          												

          需要提醒大家注意的是closeSession方法,在該方法中我們必須手工去關(guān)閉session對應(yīng)的數(shù)據(jù)庫連接,我們前面已經(jīng)提到了,UserSuppliedConnectionProvider類就是要求用戶自己提供數(shù)據(jù)庫連接已經(jīng)連接的關(guān)閉。如果沒有調(diào)用ssn.connection().close()方法,這會導(dǎo)致Struts的數(shù)據(jù)源的連接沒有被釋放。

          同理,上面提到的Struts只是一個(gè)應(yīng)用普遍的例子,實(shí)際中你可以使用任何的外部連接池,你只需要將獲取到的數(shù)據(jù)庫連接傳遞給openSession方法,并自行負(fù)責(zé)釋放數(shù)據(jù)庫連接即可。應(yīng)該說這是一種最簡單的思路,好處是對系統(tǒng)的變動(dòng)最小,兼容原來的代碼。



          回頁首


          思路二:擴(kuò)展ConnectionProvider

          Hibernate本身是通過ConnectionProvider接口來實(shí)現(xiàn)管理數(shù)據(jù)庫連接的。例如其自帶的C3P0ConnectionProvider,ProxoolConnectionProvider等。

          在這個(gè)思路中,我們希望可以直接在Hibernate的配置文件中配置數(shù)據(jù)庫連接,也就是讓Hibernate獨(dú)攬數(shù)據(jù)庫的管理,真正做到各司其職。為了更了解該接口的使用,你不妨閱讀一下Hibernate提供的上面幾個(gè)類的源碼。

          接下來我們需要編寫一個(gè)實(shí)現(xiàn)了ConnectionProvider接口的類,要求這個(gè)類能支持任何的符合DataSource接口規(guī)范的數(shù)據(jù)源,同時(shí)在Hibernate的配置文件中進(jìn)行參數(shù)的設(shè)定。首先我們假定我們的類名是DataSourceConnProvider,那我們的配置信息在hibernate.cfg.xml中看起來應(yīng)該像下面一樣



          														
          																        	<!-- Connection Pool settings -->
          <property name="connection.provider_class">
          com.liusoft.dlog4j.db.DataSourceConnProvider</property>
                  <property name="dscp.datasource">org.apache.commons.dbcp.BasicDataSource</property>
                  <property name="dscp.driverClassName">sun.jdbc.odbc.JdbcOdbcDriver</property>
                  <property name="dscp.url">jdbc:odbc:dlog4j</property>
                  <property name="dscp.username">admin</property>
                  <property name="dscp.password"></property>
                  <property name="dscp.initialSize">1</property>
                  <property name="dscp.maxActive">200</property>
                  <property name="dscp.maxWait">2000</property>
                  <property name="dscp.defaultAutoCommit">false</property>
                  <property name="dscp.defaultReadOnly">false</property>
                  <property name="dscp.removeAbandoned">true</property>
                  <property name="dscp.removeAbandonedTimeout">120</property>
          	<!--
                  <property name="dscp.defaultTransactionIsolation">1</property>
          	-->
                  <property name="dscp.poolPreparedStatements">true</property>
                  <property name="dscp.maxOpenPreparedStatements">1000</property> 
                  
          														
          												

          在上面的配置信息中,connection.provider_class是Hibernate本身用來指定不同ConnectionProvider實(shí)現(xiàn)類。接下來我們規(guī)定了我們的擴(kuò)展所使用的配置鍵值都是以dscp.開頭,同時(shí)我們使用dscp.datasource來指定具體實(shí)現(xiàn)了DataSource接口的類名,例如如果使用DBCP這個(gè)連接池,那么這個(gè)類名應(yīng)該是org.apache.commons.dbcp.BasicDataSource。對于其他以dscp.開頭的且不是dscp.datasource的配置信息都會直接賦值給DataSource的實(shí)現(xiàn)類。例如上面的配置中,driverClassName、url、username、password等配置信息都是BasicDataSource類的屬性。

          下面是我們所實(shí)現(xiàn)的DataSourceConnProvider類的源碼。



          														
          																package com.liusoft.dlog4j.db;
          
          import java.lang.reflect.Method;
          import java.sql.Connection;
          import java.sql.SQLException;
          import java.util.Iterator;
          import java.util.Properties;
          
          import javax.sql.DataSource;
          
          import org.apache.commons.beanutils.BeanUtils;
          import org.hibernate.HibernateException;
          import org.hibernate.connection.ConnectionProvider;
          
          import com.liusoft.dlog4j.Globals;
          import com.liusoft.dlog4j.util.StringUtils;
          
          /**
           * 讓Hibernate支持各種數(shù)據(jù)源
           * @author Winter Lau
           */
          public class DataSourceConnProvider implements ConnectionProvider {
          	
          	private final static String BASE_KEY = "dscp.";
          	private final static String ENCODING_KEY = "dscp.encoding";
          	private final static String DATASOURCE_KEY = "dscp.datasource";
          	
          	protected DataSource dataSource;
          	
          	/* (non-Javadoc)
          	 * @see org.hibernate.connection.ConnectionProvider#configure(java.util.Properties)
          	 */
          	public void configure(Properties props) throws HibernateException {
          		String dataSourceClass = null;
          		Properties new_props = new Properties();
          		Iterator keys = props.keySet().iterator();
          		while(keys.hasNext()){
          			String key = (String)keys.next();
          			if(DATASOURCE_KEY.equalsIgnoreCase(key)){
          				dataSourceClass = props.getProperty(key);
          			}
          			else if(key.startsWith(BASE_KEY)){
          				String value = props.getProperty(key);
          				value = StringUtils.replace(value, "{DLOG4J}", Globals.WEBAPP_PATH);
          				new_props.setProperty(key.substring(BASE_KEY.length()), value);
          			}
          		}
          		if(dataSourceClass == null)
          			throw new HibernateException("Property 'dscp.datasource' no defined.");
          		try {
          			dataSource = (DataSource)Class.forName(dataSourceClass).newInstance();
          			BeanUtils.populate(dataSource, new_props);
          		} catch (Exception e) {
          			throw new HibernateException(e);
          		}
          	}
          
          	/* (non-Javadoc)
          	 * @see org.hibernate.connection.ConnectionProvider#getConnection()
          	 */
          	public Connection getConnection() throws SQLException {		
          		final Connection conn = dataSource.getConnection();
          		if(useProxy && conn!=null){
          			return (new _Connection(conn,encoding)).getConnection();
          		}
          		return conn;
          	}
          
          	/* (non-Javadoc)
          	 * @see org.hibernate.connection.ConnectionProvider#closeConnection(java.sql.Connection)
          	 */
          	public void closeConnection(Connection conn) throws SQLException {
          		if(conn!=null && !conn.isClosed())
          			conn.close();
          	}
          
          	/* (non-Javadoc)
          	 * @see org.hibernate.connection.ConnectionProvider#close()
          	 */
          	public void close() throws HibernateException {
          		if(dataSource != null)
          		try {
          			Method mClose = dataSource.getClass().getMethod("close",null);
          			mClose.invoke(dataSource, null);
          		} catch (Exception e) {
          			throw new HibernateException(e);
          		}
          		dataSource = null;
          	}
          
          	/* (non-Javadoc)
          	 * @see org.hibernate.connection.ConnectionProvider#supportsAggressiveRelease()
          	 */
          	public boolean supportsAggressiveRelease() {
          		return false;
          	}
          
          }
          
          														
          												

          在DataSourceConnProvider類中,configure方法會在Hibernate進(jìn)行初始化的過程中被調(diào)用,我們根據(jù)配置的DataSource類名創(chuàng)建數(shù)據(jù)源實(shí)例,并將配置參數(shù)賦值給該實(shí)例后即完成了數(shù)據(jù)源的初始化。接下來就是實(shí)現(xiàn)了getConnection和closeConnection方法分別是獲取數(shù)據(jù)庫連接和關(guān)閉連接的方法。方法close用來關(guān)閉整個(gè)數(shù)據(jù)源,該方法會在Hibernate釋放時(shí)被調(diào)用。

          你也可以使用其他一些不同的數(shù)據(jù)源而不一定非是DBCP數(shù)據(jù)源。配置完畢后接下來的事情就簡單了,直接調(diào)用SessionFactory.openSession()方法獲取Session實(shí)例,直接調(diào)用session.close()釋放該實(shí)例,無需再手工去關(guān)閉session所封裝的connection接口。

          相比較上面兩種思路而言,各有千秋。如果你真的有必要擴(kuò)展Hibernate對數(shù)據(jù)源的支持,如果你沒有兼容舊代碼這個(gè)問題需要考慮的話,那我更傾向于第二種思路。因?yàn)樗沟谜麄€(gè)項(xiàng)目的各個(gè)層次分工非常清晰,而且除了ConnectionProvider 類以外應(yīng)用的代碼也相對簡單。

          posted on 2006-06-07 15:20 崛起的程序員 閱讀(963) 評論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 东光县| 张家界市| 利辛县| 都匀市| 峨边| 丹巴县| 大埔区| 昭觉县| 翼城县| 崇仁县| 莎车县| 金湖县| 西林县| 贵德县| 枞阳县| 合江县| 岳池县| 雷山县| 斗六市| 山西省| 富裕县| 塔河县| 乐平市| 寿宁县| 如皋市| 密云县| 三江| 雅江县| 宁阳县| 普兰店市| 多伦县| 瓮安县| 美姑县| 河南省| 贡山| 广昌县| 紫云| 定南县| 桐梓县| 广西| 镇江市|