先是一個(gè)好消息,在2006年國(guó)慶期間,Spring2.0正式版(http://www.springframework.org/download)和Spring2.0中文文檔(http://spring.jactiongroup.net/viewtopic.php?t=2279&sid=0906ae6a913537b249e501db5b54c181)終于發(fā)布了。
在前面的學(xué)習(xí)之后,我們已經(jīng)了解了Spring框架的核心,在本章中作者向我們講述了Spring的持久化層,向我們介紹了Srping對(duì)JDBC的支持。還向讀者介紹了Spring如何與當(dāng)前流行的一些ORM框架,如Hibernate、JDO、OJB、和iBATIS等集成。
一、學(xué)習(xí)Spring的DAO理念
DAO代表數(shù)據(jù)訪問(wèn)對(duì)象(Data Access Object)。它完美地描述了在一個(gè)應(yīng)用中DAO的角色。DAO的存在提供了讀寫數(shù)據(jù)庫(kù)中數(shù)據(jù)的一種方法。只要把這個(gè)功能通過(guò)接口暴露,應(yīng)用的其他部分就可以通過(guò)這個(gè)接口來(lái)訪問(wèn)數(shù)據(jù)庫(kù)了。下圖展示了設(shè)計(jì)數(shù)據(jù)訪問(wèn)層的合適途徑:
如上圖所示,服務(wù)對(duì)象不再和特定的數(shù)據(jù)訪問(wèn)實(shí)現(xiàn)邦定在一起,使得他們易于測(cè)試。此外,我們是通過(guò)與持久化技術(shù)無(wú)關(guān)的方式來(lái)訪問(wèn)數(shù)據(jù)訪問(wèn)層的,DAO接口不需要暴露他采用什么技術(shù)去訪問(wèn)數(shù)據(jù)。Spring幫你把數(shù)據(jù)訪問(wèn)層從應(yīng)用的其他部分隔離開來(lái)的一種方法是:提供一套貫穿整個(gè)DAO框架的一致的分級(jí)異常體系。
1.理解Spring的DataAccessException
Spring的DAO框架沒(méi)有拋出與特定技術(shù)相關(guān)的異常,Spring提供了一種方便的方法,把特定于某種技術(shù)的異常,如SQLException,轉(zhuǎn)化為自己的異常,這種異常屬于以DataAccessException為異常層次。
DataAccessException是RuntimeException,所以它是一個(gè)無(wú)需檢測(cè)的異常。意思是說(shuō),當(dāng)他們被數(shù)據(jù)訪問(wèn)層拋出時(shí),并不需要你的代碼去處理這類異常。這遵循了Spring的一般理念:異常檢查會(huì)使你的代碼到處是不相關(guān)的catch或throws語(yǔ)句,使代碼雜亂無(wú)章。對(duì)數(shù)據(jù)訪問(wèn)異常來(lái)說(shuō),這點(diǎn)尤為正確,因?yàn)槲覀冊(cè)趯戇B接數(shù)據(jù)庫(kù)和操作數(shù)據(jù)庫(kù)表的代碼中,總是充斥著大量的異常。
一般情況下,我們的數(shù)據(jù)庫(kù)訪問(wèn)API總是要拋出一個(gè)有意義的異常。JDO有它自己的異常層次。下圖是Spring2.0中定義的異常層次。
上圖所包括的異常類只是整個(gè)龐大的DataAccessException異常層次中的一部分。
2.一致的DAO支持
不管我們采用什么技術(shù),某些數(shù)據(jù)庫(kù)訪問(wèn)的步驟是必須的。如數(shù)據(jù)庫(kù)連接,操作,操作完后釋放連接。這些都是固定步驟。Spring把數(shù)據(jù)庫(kù)訪問(wèn)流程中的固定部分和可變部分分開,分別映射成兩個(gè)截然不同的類:模板(Template)和回調(diào)(Callback)。模板管理流程的固定部分,而在回調(diào)處填寫實(shí)現(xiàn)細(xì)節(jié)。如下圖所示:
在上圖中Spring的模板類處理數(shù)據(jù)訪問(wèn)的不變部分—事務(wù)控制、資源管理以及異常處理。回調(diào)接口的實(shí)現(xiàn)定義了特定于應(yīng)用的部分—創(chuàng)建statement,邦定參數(shù)以及整理結(jié)果集(ResultSet)。這樣我們只需要關(guān)心數(shù)據(jù)訪問(wèn)的邏輯就可以了。
為了便于以一種一致的方式使用各種數(shù)據(jù)庫(kù)訪問(wèn)技術(shù),如JDBC、JDO和Hibernate,Spring提供了一套抽象DAO類供我們進(jìn)行擴(kuò)展。這些抽象類提供了一些方法,通過(guò)它們我們可以獲得與當(dāng)前數(shù)據(jù)訪問(wèn)技術(shù)相關(guān)的數(shù)據(jù)源和其他配制信息。
這些類是:
l JdbcDaoSupport – JDBC數(shù)據(jù)訪問(wèn)對(duì)象的基類。
l HibernateDaoSupport – Hibernate數(shù)據(jù)訪問(wèn)對(duì)象的基類。
l JdoDaoSupport – JDO數(shù)據(jù)訪問(wèn)對(duì)象的基類。
l JpaDaoSupport – JPA數(shù)據(jù)訪問(wèn)的基類。
對(duì)于以上四個(gè)類,更詳細(xì)的內(nèi)容請(qǐng)參看Spring2.0第十章。
二、在Spring中使用JDBC
起初我們?cè)谑褂?/span>Java連接數(shù)據(jù)庫(kù)時(shí)候都使用的是JDBC,剛開始可能是對(duì)數(shù)據(jù)庫(kù)表作一個(gè)操作就會(huì)連接一次數(shù)據(jù)庫(kù),然后再將數(shù)據(jù)庫(kù)連接關(guān)閉。大概是這樣的步驟:連接數(shù)據(jù)庫(kù)-- 對(duì)某表進(jìn)行操作—關(guān)閉數(shù)據(jù)庫(kù),后來(lái)一些人對(duì)數(shù)據(jù)庫(kù)的操作進(jìn)行了封裝,將數(shù)據(jù)庫(kù)的連接操作關(guān)閉等動(dòng)作放到一個(gè)類中(http://blog.csdn.net/qutr/archive/2006/09/25/1274826.aspx),還有人設(shè)計(jì)出了數(shù)據(jù)庫(kù)連接池,使得數(shù)據(jù)庫(kù)連接所占有的資源更少,效率更高。這樣的做法,缺點(diǎn)是當(dāng)我們只需要一種操作(插入、刪除、更新、選擇)時(shí)我們要額外的多寫出20—30行代碼。優(yōu)點(diǎn)是,有利于我們對(duì)資源的控制。那么在Spring中我們有了更多更好的選擇。
1.Spring2.0中的包
Spring JDBC抽象框架由四個(gè)包構(gòu)成:core、 dataSource、object以及support。
org.springframework.jdbc.core包由JdbcTemplate類以及相關(guān)的回調(diào)接口(callback interface)和類組成。
org.springframework.jdbc.datasource包由一些用來(lái)簡(jiǎn)化DataSource訪問(wèn)的工具類,以及各種DataSource接口的簡(jiǎn)單實(shí)現(xiàn)(主要用于單元測(cè)試以及在J2EE容器之外使用JDBC)組成。工具類提供了一些靜態(tài)方法,諸如通過(guò)JNDI獲取數(shù)據(jù)連接以及在必要的情況下關(guān)閉這些連接。它支持綁定線程的連接,比如被用于DataSourceTransactionManager的連接。
org.springframework.jdbc.object包由封裝了查詢、更新以及存儲(chǔ)過(guò)程的類組成,這些類的對(duì)象都是線程安全并且可重復(fù)使用的。它們類似于JDO,與JDO的不同之處在于查詢結(jié)果與數(shù)據(jù)庫(kù)是“斷開連接”的。它們是在org.springframework.jdbc.core包的基礎(chǔ)上對(duì)JDBC更高層次的抽象。
org.springframework.jdbc.support包提供了一些SQLException的轉(zhuǎn)換類以及相關(guān)的工具類。
在JDBC處理過(guò)程中拋出的異常將被轉(zhuǎn)換成org.springframework.dao包中定義的異常。因此使用Spring JDBC進(jìn)行開發(fā)將不需要處理JDBC或者特定的RDBMS才會(huì)拋出的異常。所有的異常都是unchecked exception,這樣我們就可以對(duì)傳遞到調(diào)用者的異常進(jìn)行有選擇的捕獲
2.使用JdbcTemplate
當(dāng)我們寫JDBC的相關(guān)代碼時(shí)候,總是寫很多的代碼來(lái)維護(hù)資源的連接和釋放,還要處理各種異常情況。寫出的try-catch語(yǔ)句通常會(huì)打亂程序結(jié)構(gòu)。使用Spring JDBC會(huì)使我們的JDBC代碼非常干凈,我們不必再去寫繁瑣的statement和各種查詢語(yǔ)句。提供這一功能的就是JdbcTemplate類,它存在于org.springframework.jdbc.core包下,創(chuàng)建一個(gè)JdbcTemplate類的實(shí)例相當(dāng)簡(jiǎn)單:
JdbcTemplate jdbcTem = new JdbcTemplate(dataSource);
關(guān)于dataSource是DataSource的一個(gè)實(shí)例,DataSource在javax.sql包下,他是一個(gè)interface所以我們要實(shí)現(xiàn)他,DataSource是JDBC規(guī)范的一部分,它被視為一個(gè)通用的數(shù)據(jù)庫(kù)連接工廠。通過(guò)使用DataSource, Container或Framework可以將連接池以及事務(wù)管理的細(xì)節(jié)從應(yīng)用代碼中分離出來(lái)。在這里我們使用DriverManagerDataSource,不過(guò)DataSource有多種實(shí)現(xiàn),詳細(xì)說(shuō)明可以參考Spring參考手冊(cè)(官方文檔)。看下面的代碼示例:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gb2312&autoReconnect=true");
dataSource.setUsername("root");
dataSource.setPassword("");
當(dāng)我們?cè)O(shè)置好DataSource和得到JdbcTemplate對(duì)象后就可以開始對(duì)數(shù)據(jù)庫(kù)進(jìn)行各種查詢和更新了。
3.寫入數(shù)據(jù)
在《Spring In Action》一書中作者介紹了好幾種方法,這里只展示一種。首先,設(shè)置數(shù)據(jù):
private DataSource dataSource;
private JdbcTemplate jdbcTemlate = new JdbcTemplate(dataSource);
private Person person = new Person();
publicvoid setValue(PreparedStatement ps) throws SQLException
{
ps.setInt(0, person.getId());
ps.setString(1, person.getFirstName());
ps.setString(2, person.getLastName());
}//end setValue(...)
publicint insertPerson(Person person)
{
String insertSql = "insert into person (id, firstName, lastName) values(?, ?, ?)";
Object[] params = new Object[] {person.getId(), person.getFirstName(), person.getLastName()};
returnjdbcTemlate.update(insertSql, params);
}//end insertPerson(...)
最后一個(gè)insertPerson函數(shù)實(shí)現(xiàn)了插入一條記錄的操作,可以看到非常的簡(jiǎn)單。當(dāng)我們使用了JdbcTemplate后我們不必去擔(dān)心數(shù)據(jù)庫(kù)的關(guān)閉,再JdbcTemplate類中,在適當(dāng)?shù)牡胤揭呀?jīng)為我們將數(shù)據(jù)庫(kù)關(guān)閉了。
使用BatchPreparedStatementSetter這個(gè)接口,一次插入多條記錄。該接口有兩個(gè)方法:
void setValues(PreparedStatement ps, int i) throws SQLException;和
int getBatchSize();其中getBatchSize()告訴JdbcTemplate到底有多少語(yǔ)句要?jiǎng)?chuàng)建。看下面的代碼:
publicint[] updatePersons(final List persons)
{
String insertSql = "insert into person (id, firstName, lastName) values(?, ?, ?)";
BatchPreparedStatementSetter setter = new BatchPreparedStatementSetter(){
publicint getBatchSize(){
return persons.size();
}
publicvoid setValues(PreparedStatement ps, int index)throws SQLException {
Person person = (Person)persons.get(index);
ps.setInt(0, person.getId());
ps.setString(1, person.getFirstName());
ps.setString(2, person.getLastName());
}
};
returnjdbcTemlate.batchUpdate(insertSql, setter);
}//end updatePersons(...)
這樣就實(shí)現(xiàn)了一次插入多個(gè)記錄。
4.讀數(shù)據(jù)
在JDBC中取得一條記錄的結(jié)果需要通過(guò)ResultSet。在Spring JDBC中已經(jīng)幫我們處理了這些。通過(guò)實(shí)現(xiàn)RowCallbackHandler接口中僅有的一個(gè)方法來(lái)完成。該方法為:
void processRow(ResultSet rs) throws SQLException;
下面的代碼展示了如何使用RowCallbackHandler接口來(lái)選擇一條記錄。
public Person getPerson(final Integer id)
{
String sql = "select id, first_name, last_name from person where id = ?";
final Person person = new Person();
final Object[] params = new Object[] {id};
jdbcTemplate.query(sql, params, new RowCallbackHandler(){
publicvoid processRow(ResultSet rs) throws SQLException{
person.setId(rs.getInt("id"));
person.setFirstName(rs.getString("first_name"));
person.setLastName(rs.getString("last_name"));
}
});
return person;
}//end getPerson(...)
利用Spring JDBC提供的RowMapper接口我們可以把ResultSet中的一條記錄映射成一個(gè)對(duì)象。這個(gè)在《Spring In Action》一書中都講解的比較詳細(xì)這里就不再說(shuō)了。
值得注意的是在Spring2.0中為我們提供了一個(gè)NamedParameterJdbcTemplate類,該類為JDBC查詢提供了帶命名參數(shù)的占位符,而不止是JDBC自己的“?”,這樣使用JDBC的時(shí)候,也可以很容易的構(gòu)造出來(lái)帶占位符的動(dòng)態(tài)條件查詢,而不是參數(shù)值帶入方式的拼接SQL字符串了。(引自:Spring2.0的新特性點(diǎn)評(píng))確實(shí)也是這樣,而且在Spring的2.0文檔中,作者用了較大篇幅介紹了該類。察看Spring2.0的原代碼(該類在org.springframework.jdbc.core.namedparam包下)可以看到:
public NamedParameterJdbcTemplate(DataSource dataSource) {
Assert.notNull(dataSource, "The [dataSource] argument cannot be null.");
this.classicJdbcTemplate = new JdbcTemplate(dataSource);
}
他事實(shí)上調(diào)用了JdbcTemplate類。關(guān)于Spring JDBC的更多更詳細(xì)的內(nèi)容請(qǐng)參看Spring2.0官方文檔,上面講解的非常透徹,代碼實(shí)例也非常明了。
還有一個(gè)類值得我們關(guān)注:SimpleJdbcTemplate類,該類是JdbcTemplate類的一個(gè)包裝器(wrapper),它利用了Java 5的一些語(yǔ)言特性,特別適合在JDK5下編程的人。下面引用一個(gè)例子:
public Actor findActor(long id)
{
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper()<Actor> {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(this.getDataSource());
return simpleJdbcTemplate.queryForObject(sql, mapper, id);
}
三、Spring的ORM框架支持
Spring除了很好的封裝了JDBC外還支持以下持久層框架:
l Hibernate(http://www.hibernate.org/)
l JDO(http://java.sun.com/products/jdo/)
l iBATIS(http://ibatis.apache.org/)
l OJB(http://db.apache.org/ojb/)
在我目前手頭上的工作來(lái)看,有了JDBC已經(jīng)足夠我用了。但是學(xué)習(xí)是無(wú)止境的,看網(wǎng)上那幫人整天這個(gè)框架那個(gè)框架的大談特談,學(xué)到這里的我肯定不會(huì)停止不前的。我的決定是學(xué)習(xí)Hibernate,當(dāng)然JDO是標(biāo)準(zhǔn),iBATIS和OJB也是非常的強(qiáng)悍。
寫到這里下面應(yīng)該接著寫以上框架的一種或幾種了,那么按照我的計(jì)劃應(yīng)該最少寫點(diǎn)Hibernate的內(nèi)容,但是很不幸的是由于太忙Hibernate學(xué)的還非常淺薄有限。思前想后,我應(yīng)該停下來(lái)了。因?yàn)樽源颉?/span>Spring In Action》學(xué)習(xí)筆記開寫以來(lái),文章一篇比一篇質(zhì)量差,貼到Blog上的東西雖然是督促自己學(xué)習(xí)的,但是肯定是有人看的。為了對(duì)得起自己,對(duì)得起看文章的人,我覺定還是把質(zhì)量提高比較重要。所以到這里我要打住了。等在實(shí)踐中有了經(jīng)驗(yàn)和領(lǐng)悟,我會(huì)寫一個(gè)關(guān)于Hibernate的專題。希望這一天早日到來(lái)。
四、小結(jié)
Spring的持久化技術(shù)是多種多樣的,但是不管用什么樣的持久化技術(shù),Spring的目標(biāo)就是讓它對(duì)應(yīng)用系統(tǒng)的其他部分來(lái)說(shuō)是透明的。這樣我們?cè)诰帉懗志脤拥臅r(shí)候可以減輕工作量,減少出錯(cuò)的幾率。使我們對(duì)于數(shù)據(jù)庫(kù)的操作更為方便和簡(jiǎn)單。
最后推薦兩片文章:
n Spring2.0的新特性點(diǎn)評(píng)( http://www.javaeye.com/topic/25530 )
n 無(wú)責(zé)任評(píng)論Spring和Java企業(yè)應(yīng)用(http://www.javaeye.com/topic/25556)