隨筆-28  評論-15  文章-81  trackbacks-0

          先是一個好消息,在2006年國慶期間,Spring2.0正式版(http://www.springframework.org/download)和Spring2.0中文文檔(http://spring.jactiongroup.net/viewtopic.php?t=2279&sid=0906ae6a913537b249e501db5b54c181)終于發布了。

          在前面的學習之后,我們已經了解了Spring框架的核心,在本章中作者向我們講述了Spring的持久化層,向我們介紹了SrpingJDBC的支持。還向讀者介紹了Spring如何與當前流行的一些ORM框架,如HibernateJDO、OJB、和iBATIS等集成。

           

          一、學習SpringDAO理念

          DAO代表數據訪問對象(Data Access Object)。它完美地描述了在一個應用中DAO的角色。DAO的存在提供了讀寫數據庫中數據的一種方法。只要把這個功能通過接口暴露,應用的其他部分就可以通過這個接口來訪問數據庫了。下圖展示了設計數據訪問層的合適途徑:

           

          如上圖所示,服務對象不再和特定的數據訪問實現邦定在一起,使得他們易于測試。此外,我們是通過與持久化技術無關的方式來訪問數據訪問層的,DAO接口不需要暴露他采用什么技術去訪問數據。Spring幫你把數據訪問層從應用的其他部分隔離開來的一種方法是:提供一套貫穿整個DAO框架的一致的分級異常體系。

          1.理解SpringDataAccessException

          SpringDAO框架沒有拋出與特定技術相關的異常,Spring提供了一種方便的方法,把特定于某種技術的異常,如SQLException,轉化為自己的異常,這種異常屬于以DataAccessException為異常層次。

          DataAccessExceptionRuntimeException,所以它是一個無需檢測的異常。意思是說,當他們被數據訪問層拋出時,并不需要你的代碼去處理這類異常。這遵循了Spring的一般理念:異常檢查會使你的代碼到處是不相關的catchthrows語句,使代碼雜亂無章。對數據訪問異常來說,這點尤為正確,因為我們在寫連接數據庫和操作數據庫表的代碼中,總是充斥著大量的異常。

              一般情況下,我們的數據庫訪問API總是要拋出一個有意義的異常。JDO有它自己的異常層次。下圖是Spring2.0中定義的異常層次。

          上圖所包括的異常類只是整個龐大的DataAccessException異常層次中的一部分。

          2.一致的DAO支持

          不管我們采用什么技術,某些數據庫訪問的步驟是必須的。如數據庫連接,操作,操作完后釋放連接。這些都是固定步驟。Spring把數據庫訪問流程中的固定部分和可變部分分開,分別映射成兩個截然不同的類:模板(Template)和回調(Callback)。模板管理流程的固定部分,而在回調處填寫實現細節。如下圖所示:

          在上圖中Spring的模板類處理數據訪問的不變部分事務控制、資源管理以及異常處理。回調接口的實現定義了特定于應用的部分創建statement,邦定參數以及整理結果集(ResultSet)。這樣我們只需要關心數據訪問的邏輯就可以了。

          為了便于以一種一致的方式使用各種數據庫訪問技術,如JDBC、JDOHibernateSpring提供了一套抽象DAO類供我們進行擴展。這些抽象類提供了一些方法,通過它們我們可以獲得與當前數據訪問技術相關的數據源和其他配制信息。

          這些類是:

          l         JdbcDaoSupport – JDBC數據訪問對象的基類。

          l         HibernateDaoSupport – Hibernate數據訪問對象的基類。

          l         JdoDaoSupport – JDO數據訪問對象的基類。

          l         JpaDaoSupport – JPA數據訪問的基類。

          對于以上四個類,更詳細的內容請參看Spring2.0第十章。

           

          二、在Spring中使用JDBC

          起初我們在使用Java連接數據庫時候都使用的是JDBC,剛開始可能是對數據庫表作一個操作就會連接一次數據庫,然后再將數據庫連接關閉。大概是這樣的步驟:連接數據庫-- 對某表進行操作關閉數據庫,后來一些人對數據庫的操作進行了封裝,將數據庫的連接操作關閉等動作放到一個類中(http://blog.csdn.net/qutr/archive/2006/09/25/1274826.aspx),還有人設計出了數據庫連接池,使得數據庫連接所占有的資源更少,效率更高。這樣的做法,缺點是當我們只需要一種操作(插入、刪除、更新、選擇)時我們要額外的多寫出20—30行代碼。優點是,有利于我們對資源的控制。那么在Spring中我們有了更多更好的選擇。

          1Spring2.0中的包

          Spring JDBC抽象框架由四個包構成:core dataSource、object以及support。

          org.springframework.jdbc.core包由JdbcTemplate類以及相關的回調接口(callback interface)和類組成。

          org.springframework.jdbc.datasource包由一些用來簡化DataSource訪問的工具類,以及各種DataSource接口的簡單實現(主要用于單元測試以及在J2EE容器之外使用JDBC)組成。工具類提供了一些靜態方法,諸如通過JNDI獲取數據連接以及在必要的情況下關閉這些連接。它支持綁定線程的連接,比如被用于DataSourceTransactionManager的連接。

          org.springframework.jdbc.object包由封裝了查詢、更新以及存儲過程的類組成,這些類的對象都是線程安全并且可重復使用的。它們類似于JDO,與JDO的不同之處在于查詢結果與數據庫是斷開連接的。它們是在org.springframework.jdbc.core包的基礎上對JDBC更高層次的抽象。

          org.springframework.jdbc.support包提供了一些SQLException的轉換類以及相關的工具類。

          JDBC處理過程中拋出的異常將被轉換成org.springframework.dao包中定義的異常。因此使用Spring JDBC進行開發將不需要處理JDBC或者特定的RDBMS才會拋出的異常。所有的異常都是unchecked exception,這樣我們就可以對傳遞到調用者的異常進行有選擇的捕獲

          2.使用JdbcTemplate

          當我們寫JDBC的相關代碼時候,總是寫很多的代碼來維護資源的連接和釋放,還要處理各種異常情況。寫出的try-catch語句通常會打亂程序結構。使用Spring JDBC會使我們的JDBC代碼非常干凈,我們不必再去寫繁瑣的statement和各種查詢語句。提供這一功能的就是JdbcTemplate類,它存在于org.springframework.jdbc.core包下,創建一個JdbcTemplate類的實例相當簡單:

          JdbcTemplate jdbcTem = new JdbcTemplate(dataSource);

          關于dataSourceDataSource的一個實例,DataSourcejavax.sql包下,他是一個interface所以我們要實現他,DataSourceJDBC規范的一部分,它被視為一個通用的數據庫連接工廠。通過使用DataSource, ContainerFramework可以將連接池以及事務管理的細節從應用代碼中分離出來。在這里我們使用DriverManagerDataSource,不過DataSource有多種實現,詳細說明可以參考Spring參考手冊(官方文檔)??聪旅娴拇a示例:

              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("");

          當我們設置好DataSource和得到JdbcTemplate對象后就可以開始對數據庫進行各種查詢和更新了。

          3.寫入數據

          在《Spring In Action》一書中作者介紹了好幾種方法,這里只展示一種。首先,設置數據:

              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(...)

          最后一個insertPerson函數實現了插入一條記錄的操作,可以看到非常的簡單。當我們使用了JdbcTemplate后我們不必去擔心數據庫的關閉,再JdbcTemplate類中,在適當的地方已經為我們將數據庫關閉了。

          使用BatchPreparedStatementSetter這個接口,一次插入多條記錄。該接口有兩個方法:

          void setValues(PreparedStatement ps, int i) throws SQLException;

          int getBatchSize();其中getBatchSize()告訴JdbcTemplate到底有多少語句要創建。看下面的代碼:

              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(...)

          這樣就實現了一次插入多個記錄。

          4.讀數據

          JDBC中取得一條記錄的結果需要通過ResultSet。在Spring JDBC中已經幫我們處理了這些。通過實現RowCallbackHandler接口中僅有的一個方法來完成。該方法為:

          void processRow(ResultSet rs) throws SQLException;

          下面的代碼展示了如何使用RowCallbackHandler接口來選擇一條記錄。

              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中的一條記錄映射成一個對象。這個在《Spring In Action》一書中都講解的比較詳細這里就不再說了。

          值得注意的是在Spring2.0中為我們提供了一個NamedParameterJdbcTemplate類,該類為JDBC查詢提供了帶命名參數的占位符,而不止是JDBC自己的?,這樣使用JDBC的時候,也可以很容易的構造出來帶占位符的動態條件查詢,而不是參數值帶入方式的拼接SQL字符串了。(引自:Spring2.0的新特性點評)確實也是這樣,而且在Spring2.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);

              }

          他事實上調用了JdbcTemplate類。關于Spring JDBC的更多更詳細的內容請參看Spring2.0官方文檔,上面講解的非常透徹,代碼實例也非常明了。

          還有一個類值得我們關注:SimpleJdbcTemplate類,該類是JdbcTemplate類的一個包裝器(wrapper),它利用了Java 5的一些語言特性,特別適合在JDK5下編程的人。下面引用一個例子:

          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);

          }

           

          三、SpringORM框架支持

          Spring除了很好的封裝了JDBC外還支持以下持久層框架:

          l         Hibernatehttp://www.hibernate.org/

          l         JDOhttp://java.sun.com/products/jdo/

          l         iBATIShttp://ibatis.apache.org/

          l         OJBhttp://db.apache.org/ojb/

          在我目前手頭上的工作來看,有了JDBC已經足夠我用了。但是學習是無止境的,看網上那幫人整天這個框架那個框架的大談特談,學到這里的我肯定不會停止不前的。我的決定是學習Hibernate,當然JDO是標準,iBATISOJB也是非常的強悍。

          寫到這里下面應該接著寫以上框架的一種或幾種了,那么按照我的計劃應該最少寫點Hibernate的內容,但是很不幸的是由于太忙Hibernate學的還非常淺薄有限。思前想后,我應該停下來了。因為自打《Spring In Action》學習筆記開寫以來,文章一篇比一篇質量差,貼到Blog上的東西雖然是督促自己學習的,但是肯定是有人看的。為了對得起自己,對得起看文章的人,我覺定還是把質量提高比較重要。所以到這里我要打住了。等在實踐中有了經驗和領悟,我會寫一個關于Hibernate的專題。希望這一天早日到來。

           

          四、小結

          Spring的持久化技術是多種多樣的,但是不管用什么樣的持久化技術,Spring的目標就是讓它對應用系統的其他部分來說是透明的。這樣我們在編寫持久層的時候可以減輕工作量,減少出錯的幾率。使我們對于數據庫的操作更為方便和簡單。

          最后推薦兩片文章:

          n         Spring2.0的新特性點評( http://www.javaeye.com/topic/25530

          n         無責任評論SpringJava企業應用(http://www.javaeye.com/topic/25556

          posted on 2007-10-23 21:47 譚明 閱讀(757) 評論(0)  編輯  收藏 所屬分類: Spring
          主站蜘蛛池模板: SHOW| 杨浦区| 孟连| 天台县| 博罗县| 顺义区| 邵东县| 桂林市| 龙井市| 保山市| 博乐市| 阳城县| 惠安县| 界首市| 文昌市| 鹤庆县| 普兰县| 邻水| 华蓥市| 卢氏县| 铜山县| 宁晋县| 高台县| 仁怀市| 盐城市| 华池县| 永新县| 边坝县| 夏津县| 襄汾县| 惠州市| 通道| 长岭县| 香格里拉县| 昆山市| 溆浦县| 临高县| 成都市| 广安市| 德格县| 炎陵县|