Hibernate內嵌了對C3P0,Proxool,JNDI數據源等數據庫連接池的支持。但當我們需要使用除了這幾個數據源外的其他數據源的時候就有問題了,例如我們需要用Apache的開源連接池項目DBCP,或者說我們想要使用多數JDBC驅動程序中自帶的XxxxDataSource時,Hibernate就沒有提供對這方面的支持。慶幸的是Hibernate做為一個強大的數據持久層組件,它在實現數據庫連接方面的擴展性也是非常強大的。本文將介紹兩種如何在Hibernate項目中使用自定義數據源的方法。
本文假設你已經有Hibernate的開發經驗。
在開始之前應該先明確你的項目中的具體情況,也就是確認Hibernate內嵌的組件是否真的無法支持你的應用需要。例如C3P0或者Proxool已經可以滿足大部分數據庫的需要,又或者你的數據源是在應用服務器中配置的,那么你也沒有必要進行擴展,你可以直接使用DatasourceConnectionProvider來讓Hibernate使用你所定義的數據源。
那么什么時候你需要擴展Hibernate對數據源的支持呢?可能你永遠也用不上,但我一直在用。我用的原因可能不能成為正當的理由,因為C3P0或者Proxool總有些小地方的不足讓我不爽,個人更偏向于DBCP連接池。或許本文應該改名為《讓Hibernate支持DBCP數據源》,其實DBCP只不過是我的一個具體的例子,本文具有更普遍的應用意義。下面我們具體介紹兩種不同的擴展思路。
思路一:使用外部定義數據源
假設我們已經有了一個WEB項目,該項目采用了Struts框架,而且我們已經在Struts中配置了數據源,也有不少的代碼是依賴這個數據源運行的?,F在我們需要給項目中加入對Hibernate的支持,但又不想去修改舊的已經成功穩定運行的代碼了。那我們該怎么辦?如果同樣在Hibernate配置一個數據源指到同一個數據庫,相信你也不樂意這樣干,因為一旦配置上有修改那么Struts和Hibernate的配置都需要修改,這個也只是麻煩一點而已,最要命的是沒法讓原有的代碼和Hibernate共用一個數據庫連接,因此事務處理也就無從談起。
說那么多理由,無非就是為了讓Hibernate可以使用Struts中配置的數據源,而我們暫且不去考慮這是否是最好的解決方法。
在Hibernate中有一個UserSuppliedConnectionProvider類,其實這個類什么也不干,你一旦讓它干點啥吧,它還凈出異常,搞得你很是惱火。在Hibernate中,這個類的含義是要求用戶自己來提供數據庫連接的獲取方法,同時當然也要自己負責關閉連接。
為了使用Struts中配置的數據源,我們就不能直接調用SessionFactory.openSession()方法來獲取Session實例,因為你如果沒有在Hibernate中配置任何的數據庫連接,那Hibernate會默認讓UserSuppliedConnectionProvider類來跟你搗亂,你會收到很多異常信息,反復提醒我們必須自己提供數據庫連接!我們要做還是調用openSession方法,不同的是需要先從Struts的數據源中獲取數據庫連接,然后傳遞該連接給openSession方法(參照 SessionFactory.openSession(Connection) 方法)。
下面是我寫的一個代碼片斷
//獲取Session實例
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方法,在該方法中我們必須手工去關閉session對應的數據庫連接,我們前面已經提到了,UserSuppliedConnectionProvider類就是要求用戶自己提供數據庫連接已經連接的關閉。如果沒有調用ssn.connection().close()方法,這會導致Struts的數據源的連接沒有被釋放。
同理,上面提到的Struts只是一個應用普遍的例子,實際中你可以使用任何的外部連接池,你只需要將獲取到的數據庫連接傳遞給openSession方法,并自行負責釋放數據庫連接即可。應該說這是一種最簡單的思路,好處是對系統的變動最小,兼容原來的代碼。
思路二:擴展ConnectionProvider
Hibernate本身是通過ConnectionProvider接口來實現管理數據庫連接的。例如其自帶的C3P0ConnectionProvider,ProxoolConnectionProvider等。
在這個思路中,我們希望可以直接在Hibernate的配置文件中配置數據庫連接,也就是讓Hibernate獨攬數據庫的管理,真正做到各司其職。為了更了解該接口的使用,你不妨閱讀一下Hibernate提供的上面幾個類的源碼。
接下來我們需要編寫一個實現了ConnectionProvider接口的類,要求這個類能支持任何的符合DataSource接口規范的數據源,同時在Hibernate的配置文件中進行參數的設定。首先我們假定我們的類名是DataSourceConnProvider,那我們的配置信息在hibernate.cfg.xml中看起來應該像下面一樣
<!-- 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實現類。接下來我們規定了我們的擴展所使用的配置鍵值都是以dscp.開頭,同時我們使用dscp.datasource來指定具體實現了DataSource接口的類名,例如如果使用DBCP這個連接池,那么這個類名應該是org.apache.commons.dbcp.BasicDataSource。對于其他以dscp.開頭的且不是dscp.datasource的配置信息都會直接賦值給DataSource的實現類。例如上面的配置中,driverClassName、url、username、password等配置信息都是BasicDataSource類的屬性。
下面是我們所實現的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支持各種數據源
* @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進行初始化的過程中被調用,我們根據配置的DataSource類名創建數據源實例,并將配置參數賦值給該實例后即完成了數據源的初始化。接下來就是實現了getConnection和closeConnection方法分別是獲取數據庫連接和關閉連接的方法。方法close用來關閉整個數據源,該方法會在Hibernate釋放時被調用。
你也可以使用其他一些不同的數據源而不一定非是DBCP數據源。配置完畢后接下來的事情就簡單了,直接調用SessionFactory.openSession()方法獲取Session實例,直接調用session.close()釋放該實例,無需再手工去關閉session所封裝的connection接口。
相比較上面兩種思路而言,各有千秋。如果你真的有必要擴展Hibernate對數據源的支持,如果你沒有兼容舊代碼這個問題需要考慮的話,那我更傾向于第二種思路。因為它使得整個項目的各個層次分工非常清晰,而且除了ConnectionProvider 類以外應用的代碼也相對簡單。
posted on 2006-06-07 15:20
崛起的程序員 閱讀(958)
評論(0) 編輯 收藏 所屬分類:
java