空間站

          北極心空

            BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
            15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

          級別: 中級

          陳 雄華 (quickselect@163.com), 技術(shù)總監(jiān), 寶寶淘網(wǎng)絡(luò)科技有限公司

          2007 年 7 月 30 日

          本文講解了在 Spring 中處理 LOB 數(shù)據(jù)的原理和方法,對于 Spring JDBC 以及 Spring 所集成的第三方 ORM 框架(包括 JPA、Hibernate 和 iBatis)如何處理 LOB 數(shù)據(jù)進(jìn)行了闡述。

          概述

          LOB 代表大對象數(shù)據(jù),包括 BLOB 和 CLOB 兩種類型,前者用于存儲大塊的二進(jìn)制數(shù)據(jù),如圖片數(shù)據(jù),視頻數(shù)據(jù)等,而后者用于存儲長文本數(shù)據(jù),如論壇的帖子內(nèi)容,產(chǎn)品的詳細(xì)描述等。值得注意的是:在不同的數(shù)據(jù)庫中,大對象對應(yīng)的字段類型是不盡相同的,如 DB2 對應(yīng) BLOB/CLOB,MySql 對應(yīng) BLOB/LONGTEXT,SqlServer 對應(yīng) IMAGE/TEXT。需要指出的是,有些數(shù)據(jù)庫的大對象類型可以象簡單類型一樣訪問,如 MySql 的 LONGTEXT 的操作方式和 VARCHAR 類型一樣。在一般情況下, LOB 類型數(shù)據(jù)的訪問方式不同于其它簡單類型的數(shù)據(jù),我們經(jīng)常會以流的方式操作 LOB 類型的數(shù)據(jù)。此外,LOB 類型數(shù)據(jù)的訪問不是線程安全的,需要為其單獨(dú)分配相應(yīng)的數(shù)據(jù)庫資源,并在操作完成后釋放資源。最后,Oracle 9i 非常有個(gè)性地采用非 JDBC 標(biāo)準(zhǔn)的 API 操作 LOB 數(shù)據(jù)。所有這些情況給編寫操作 LOB 類型數(shù)據(jù)的程序帶來挑戰(zhàn),Spring 在 org.springframework.jdbc.support.lob 包中為我們提供了相應(yīng)的幫助類,以便我們輕松應(yīng)對這頭攔路虎。

          Spring 大大降低了我們處理 LOB 數(shù)據(jù)的難度。首先,Spring 提供了 NativeJdbcExtractor 接口,您可以在不同環(huán)境里選擇相應(yīng)的實(shí)現(xiàn)類從數(shù)據(jù)源中獲取本地 JDBC 對象;其次,Spring 通過 LobCreator 接口取消了不同數(shù)據(jù)廠商操作 LOB 數(shù)據(jù)的差別,并提供了創(chuàng)建 LobCreator 的 LobHandler 接口,您只要根據(jù)底層數(shù)據(jù)庫類型選擇合適的 LobHandler 進(jìn)行配置即可。

          本文將詳細(xì)地講述通過 Spring JDBC 插入和訪問 LOB 數(shù)據(jù)的具體過程。不管是以塊的方式還是以流的方式,您都可以通過 LobCreator 和 LobHandler 方便地訪問 LOB 數(shù)據(jù)。對于 ORM 框架來說,JPA 擁有自身處理 LOB 數(shù)據(jù)的配置類型,Spring 為 Hibernate 和 iBatis 分別提供了 LOB 數(shù)據(jù)類型的配置類,您僅需要使用這些類進(jìn)行簡單的配置就可以像普通類型一樣操作 LOB 類型數(shù)據(jù)。





          回頁首


          本地 JDBC 對象

          當(dāng)您在 Web 應(yīng)用服務(wù)器或 Spring 中配置數(shù)據(jù)源時(shí),從數(shù)據(jù)源中返回的數(shù)據(jù)連接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類,這是因?yàn)閿?shù)據(jù)源需要改變數(shù)據(jù)連接一些原有的行為以便對其進(jìn)行控制:如調(diào)用 Connection#close() 方法時(shí),將數(shù)據(jù)連接返回到連接池中而非將其真的關(guān)閉。

          在訪問 LOB 數(shù)據(jù)時(shí),根據(jù)數(shù)據(jù)庫廠商的不同,可能需要使用被代理前的本地 JDBC 對象(如 DB2Connection 或 DB2ResultSet)特有的 API。為了從數(shù)據(jù)源中獲取本地 JDBC 對象, Spring 定義了 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 接口并提供了相應(yīng)的實(shí)現(xiàn)類。NativeJdbcExtractor 定義了從數(shù)據(jù)源中抽取本地 JDBC 對象的若干方法:

          方法 說明
          Connection getNativeConnection(Connection con) 獲取本地 Connection 對象
          Connection getNativeConnectionFromStatement(Statement stmt) 獲取本地 Statement 對象
          PreparedStatement getNativePreparedStatement(PreparedStatement ps) 獲取本地 PreparedStatement 對象
          ResultSet getNativeResultSet(ResultSet rs) 獲取本地 ResultSet 對象
          CallableStatement getNativeCallableStatement(CallableStatement cs) 獲取本地 CallableStatement 對象
          ?

          有些簡單的數(shù)據(jù)源僅對 Connection 對象進(jìn)行代理,這時(shí)可以直接使用 SimpleNativeJdbcExtractor 實(shí)現(xiàn)類。但有些數(shù)據(jù)源(如 Jakarta Commons DBCP)會對所有的 JDBC 對象進(jìn)行代理,這時(shí),就需要根據(jù)具體的情況選擇適合的抽取器實(shí)現(xiàn)類了。下表列出了不同數(shù)據(jù)源本地 JDBC 對象抽取器的實(shí)現(xiàn)類:

          數(shù)據(jù)源類型 說明
          WebSphere 4 及以上版本的數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor
          WebLogic 6.1+ 及以上版本的數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.WebLogicNativeJdbcExtractor
          JBoss 3.2.4 及以上版本的數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.JBossNativeJdbcExtractor
          C3P0 數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor
          DBCP 數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor
          ObjectWeb 的 XAPool 數(shù)據(jù)源 org.springframework.jdbc.support.nativejdbc.XAPoolNativeJdbcExtractor

          下面的代碼演示了從 DBCP 數(shù)據(jù)源中獲取 DB2 的本地?cái)?shù)據(jù)庫連接 DB2Connection 的方法:


          清單 1. 獲取本地?cái)?shù)據(jù)庫連接
          package com.baobaotao.dao.jdbc;
                      import java.sql.Connection;
                      import COM.ibm.db2.jdbc.net.DB2Connection;
                      import org.springframework.jdbc.core.support.JdbcDaoSupport;
                      import org.springframework.jdbc.datasource.DataSourceUtils;
                      public class PostJdbcDao extends JdbcDaoSupport implements PostDao {
                      public void getNativeConn(){
                      try {
                      Connection conn = DataSourceUtils.getConnection(getJdbcTemplate()
                      .getDataSource()); ① 使用 DataSourceUtils 從模板類中獲取連接
                      ② 使用模板類的本地 JDBC 抽取器獲取本地的 Connection
                      conn = getJdbcTemplate().getNativeJdbcExtractor().getNativeConnection(conn);
                      DB2Connection db2conn = (DB2Connection) conn; ③ 這時(shí)可以強(qiáng)制進(jìn)行類型轉(zhuǎn)換了
                      …
                      } catch (Exception e) {
                      e.printStackTrace();
                      }
                      }
                      }
                      

          在 ① 處我們通過 DataSourceUtils 獲取當(dāng)前線程綁定的數(shù)據(jù)連接,為了使用線程上下文相關(guān)的事務(wù),通過 DataSourceUtils 從數(shù)據(jù)源中獲取連接是正確的做法,如果直接通過 dateSource 獲取連接,則將得到一個(gè)和當(dāng)前線程上下文無關(guān)的數(shù)據(jù)連接實(shí)例。

          JdbcTemplate 可以在配置時(shí)注入一個(gè)本地 JDBC 對象抽取器,要使代碼 清單 1 正確運(yùn)行,我們必須進(jìn)行如下配置:


          清單 2. 為 JdbcTemplate 裝配本地 JDBC 對象抽取器
          …
                      <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
                      destroy-method="close">
                      <property name="driverClassName"
                      value="${jdbc.driverClassName}" />
                      <property name="url" value="${jdbc.url}" />
                      <property name="username" value="${jdbc.username}" />
                      <property name="password" value="${jdbc.password}" />
                      </bean>
                      ① 定義 DBCP 數(shù)據(jù)源的 JDBC 本地對象抽取器
                      <bean id="nativeJdbcExtractor"
                      class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"
                      lazy-init="true" />
                      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                      <property name="dataSource" ref="dataSource" />
                      ② 設(shè)置抽取器
                      <property name="nativeJdbcExtractor" ref="nativeJdbcExtractor"/>
                      </bean>
                      <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
                      <property name="jdbcTemplate" ref="jdbcTemplate" />
                      </bean>
                      

          在獲取 DB2 的本地 Connection 實(shí)例后,我們就可以使用該對象的一些特有功能了,如使用 DB2Connection 的特殊 API 對 LOB 對象進(jìn)行操作。

          LobCreator

          雖然 JDBC 定義了兩個(gè)操作 LOB 類型的接口:java.sql.Blobjava.sql.Clob,但有些廠商的 JDBC 驅(qū)動程序并不支持這兩個(gè)接口。為此,Spring 定義了一個(gè)獨(dú)立于 java.sql.Blob/ClobLobCreator 接口,以統(tǒng)一的方式操作各種數(shù)據(jù)庫的 LOB 類型數(shù)據(jù)。因?yàn)?LobCreator 本身持有 LOB 所對應(yīng)的數(shù)據(jù)庫資源,所以它不是線程安全的,一個(gè) LobCreator 只能操作一個(gè) LOB 數(shù)據(jù)。

          為了方便在 PreparedStatement 中使用 LobCreator,您可以直接使用 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 方法。下面對 LobCreator 接口中的方法進(jìn)行簡要說明:

          方法 說明
          void close() 關(guān)閉會話,并釋放 LOB 資源
          void setBlobAsBinaryStream(PreparedStatement ps, int paramIndex, InputStream contentStream, int contentLength) 通過流填充 BLOB 數(shù)據(jù)
          void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) 通過二進(jìn)制數(shù)據(jù)填充 BLOB 數(shù)據(jù)
          void setClobAsAsciiStream(PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) 通過 Ascii 字符流填充 CLOB 數(shù)據(jù)
          void setClobAsCharacterStream(PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength) 通過 Unicode 字符流填充 CLOB 數(shù)據(jù)
          void setClobAsString(PreparedStatement ps, int paramIndex, String content) 通過字符串填充 CLOB 數(shù)據(jù)

          LobHandler

          LobHandler 接口為操作 BLOB/CLOB 提供了統(tǒng)一訪問接口,而不管底層數(shù)據(jù)庫究竟是以大對象的方式還是以一般數(shù)據(jù)類型的方式進(jìn)行操作。此外,LobHandler 還充當(dāng)了 LobCreator 的工廠類。

          大部分?jǐn)?shù)據(jù)庫廠商的 JDBC 驅(qū)動程序(如 DB2)都以 JDBC 標(biāo)準(zhǔn)的 API 操作 LOB 數(shù)據(jù),但 Oracle 9i 及以前的 JDBC 驅(qū)動程序采用了自己的 API 操作 LOB 數(shù)據(jù),Oracle 9i 直接使用自己的 API 操作 LOB 數(shù)據(jù),且不允許通過 PreparedStatement 的 setAsciiStream()setBinaryStream()setCharacterStream() 等方法填充流數(shù)據(jù)。Spring 提供 LobHandler 接口主要是為了遷就 Oracle 特立獨(dú)行的作風(fēng)。所以 Oracle 必須使用 OracleLobHandler 實(shí)現(xiàn)類,而其它的數(shù)據(jù)庫統(tǒng)一使用 DefaultLobHandler 就可以了。Oracle 10g 改正了 Oracle 9i 這個(gè)異化的風(fēng)格,終于天下歸一了,所以 Oracle 10g 也可以使用 DefaultLobHandler。 下面,我們來看一下 LobHandler 接口的幾個(gè)重要方法:

          方法 說明
          InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) 從結(jié)果集中返回 InputStream,通過 InputStream 讀取 BLOB 數(shù)據(jù)
          byte[] getBlobAsBytes(ResultSet rs, int columnIndex) 以二進(jìn)制數(shù)據(jù)的方式獲取結(jié)果集中的 BLOB 數(shù)據(jù);
          InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) 從結(jié)果集中返回 InputStream,通過 InputStreamn 以 Ascii 字符流方式讀取 BLOB 數(shù)據(jù)
          Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) 從結(jié)果集中獲取 Unicode 字符流 Reader,并通過 Reader以Unicode 字符流方式讀取 CLOB 數(shù)據(jù)
          String getClobAsString(ResultSet rs, int columnIndex) 從結(jié)果集中以字符串的方式獲取 CLOB 數(shù)據(jù)
          LobCreator getLobCreator() 生成一個(gè)會話相關(guān)的 LobCreator 對象




          回頁首


          在 Spring JDBC 中操作 LOB 數(shù)據(jù)

          插入 LOB 數(shù)據(jù)

          假設(shè)我們有一個(gè)用于保存論壇帖子的 t_post 表,擁有兩個(gè) LOB 字段,其中 post_text 是 CLOB 類型,而 post_attach 是 BLOB 類型。下面,我們來編寫插入一個(gè)帖子記錄的代碼:


          清單 3. 添加 LOB 字段數(shù)據(jù)
          package com.baobaotao.dao.jdbc;
                      …
                      import java.sql.PreparedStatement;
                      import java.sql.SQLException;
                      import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
                      import org.springframework.jdbc.support.lob.LobCreator;
                      import org.springframework.jdbc.support.lob.LobHandler;
                      public class PostJdbcDao extends JdbcDaoSupport implements PostDao {
                      private LobHandler lobHandler; ① 定義 LobHandler 屬性
                      public LobHandler getLobHandler() {
                      return lobHandler;
                      }
                      public void setLobHandler(LobHandler lobHandler) {
                      this.lobHandler = lobHandler;
                      }
                      public void addPost(final Post post) {
                      String sql = " INSERT INTO t_post(post_id,user_id,post_text,post_attach)"
                      + " VALUES(?,?,?,?)";
                      getJdbcTemplate().execute(sql,
                      new AbstractLobCreatingPreparedStatementCallback(this.lobHandler) { ②
                      protected void setValues(PreparedStatement ps,LobCreator lobCreator)
                      throws SQLException {
                      ps.setInt(1, 1);
                      ps.setInt(2, post.getUserId());
                      ③ 設(shè)置 CLOB 字段
                      lobCreator.setClobAsString(ps, 3, post.getPostText());
                      ④ 設(shè)置 BLOB 字段
                      lobCreator.setBlobAsBytes(ps, 4, post.getPostAttach());
                      }
                      });
                      }
                      …
                      }
                      

          首先,我們在 PostJdbcDao 中引入了一個(gè) LobHandler 屬性,如 ① 所示,并通過 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 方法完成插入 LOB 數(shù)據(jù)的操作。我們通過匿名內(nèi)部類的方式定義 LobCreatingPreparedStatementCallback 抽象類的子類,其構(gòu)造函數(shù)需要一個(gè) LobHandler 入?yún)ⅲ?② 所示。在匿名類中實(shí)現(xiàn)了父類的抽象方法 setValues(PreparedStatement ps,LobCreator lobCreator),在該方法中通過 lobCreator 操作 LOB 對象,如 ③、④ 所示,我們分別通過字符串和二進(jìn)制數(shù)組填充 BLOB 和 CLOB 的數(shù)據(jù)。您同樣可以使用流的方式填充 LOB 數(shù)據(jù),僅需要調(diào)用 lobCreator 相應(yīng)的流填充方法即可。

          我們需要調(diào)整 Spring 的配置文件以配合我們剛剛定義的 PostJdbcDao。假設(shè)底層數(shù)據(jù)庫是 Oracle,可以采用以下的配置方式:


          清單 4. Oracle 數(shù)據(jù)庫的 LobHandler 配置
          …
                      <bean id="nativeJdbcExtractor"
                      class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"
                      lazy-init="true"/>
                      <bean id="oracleLobHandler"
                      class="org.springframework.jdbc.support.lob.OracleLobHandler"
                      lazy-init="true">
                      <property name="nativeJdbcExtractor" ref="nativeJdbcExtractor"/> ① 設(shè)置本地 Jdbc 對象抽取器
                      </bean>
                      <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
                      <property name="lobHandler" ref="oracleLobHandler"/> ② 設(shè)置 LOB 處理器
                      </bean>
                      

          大家可能已經(jīng)注意到 nativeJdbcExtractororacleLobHandler Bean 都設(shè)置為 lazy-init="true",這是因?yàn)?nativeJdbcExtractor 需要通過運(yùn)行期的反射機(jī)制獲取底層的 JDBC 對象,所以需要避免在 Spring 容器啟動時(shí)就實(shí)例化這兩個(gè) Bean。

          LobHandler 需要訪問本地 JDBC 對象,這一任務(wù)委托給 NativeJdbcExtractor Bean 來完成,因此我們在 ① 處為 LobHandler 注入了一個(gè) nativeJdbcExtractor。最后,我們把 lobHandler Bean 注入到需要進(jìn)行 LOB 數(shù)據(jù)訪問操作的 PostJdbcDao 中,如 ② 所示。

          如果底層數(shù)據(jù)庫是 DB2、SQL Server、MySQL 等非 Oracle 的其它數(shù)據(jù)庫,則只要簡單配置一個(gè) DefaultLobHandler 就可以了,如下所示:


          清單 5. 一般數(shù)據(jù)庫 LobHandler 的配置
          <bean id="defaultLobHandler"
                      class="org.springframework.jdbc.support.lob.DefaultLobHandler"
                      lazy-init="true"/>
                      <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
                      <property name="lobHandler" ref=" defaultLobHandler"/>
                      <property name="jdbcTemplate" ref="jdbcTemplate" />
                      </bean>
                      

          DefaultLobHandler 只是簡單地代理標(biāo)準(zhǔn) JDBC 的 PreparedStatement 和 ResultSet 對象,由于并不需要訪問數(shù)據(jù)庫驅(qū)動本地的 JDBC 對象,所以它不需要 NativeJdbcExtractor 的幫助。您可以通過以下的代碼測試 PostJdbcDao 的 addPost() 方法:


          清單 6. 測試 PostJdbcDao 的 addPost() 方法
          package com.baobaotao.dao.jdbc;
                      import org.springframework.core.io.ClassPathResource;
                      import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
                      import org.springframework.util.FileCopyUtils;
                      import com.baobaotao.dao.PostDao;
                      import com.baobaotao.domain.Post;
                      public class TestPostJdbcDaoextends AbstractDependencyInjectionSpringContextTests {
                      private PostDao postDao;
                      public void setPostDao(PostDao postDao) {
                      this.postDao = postDao;
                      }
                      protected String[] getConfigLocations() {
                      return new String[]{"classpath:applicationContext.xml"};
                      }
                      public void testAddPost() throws Throwable{
                      Post post = new Post();
                      post.setPostId(1);
                      post.setUserId(2);
                      ClassPathResource res = new ClassPathResource("temp.jpg"); ① 獲取圖片資源
                      byte[] mockImg = FileCopyUtils.copyToByteArray(res.getFile());  ② 讀取圖片文件的數(shù)據(jù)
                      post.setPostAttach(mockImg);
                      post.setPostText("測試帖子的內(nèi)容");
                      postDao.addPost(post);
                      }
                      }
                      

          這里,有幾個(gè)知識點(diǎn)需要稍微解釋一下:AbstractDependencyInjectionSpringContextTests 是 Spring 專門為測試提供的類,它能夠直接從 IoC 容器中裝載 Bean。此外,我們使用了 ClassPathResource 加載圖片資源,并通過 FileCopyUtils 讀取文件的數(shù)據(jù)。ClassPathResourceFileCopyUtils 都是 Spring 提供的非常實(shí)用的工具類。

          以塊數(shù)據(jù)方式讀取 LOB 數(shù)據(jù)

          您可以直接用數(shù)據(jù)塊的方式讀取 LOB 數(shù)據(jù):用 String 讀取 CLOB 字段的數(shù)據(jù),用 byte[] 讀取 BLOB 字段的數(shù)據(jù)。在 PostJdbcDao 中添加一個(gè) getAttachs() 方法,以便獲取某一用戶的所有帶附件的帖子:


          清單 7. 以塊數(shù)據(jù)訪問 LOB 數(shù)據(jù)
          public List getAttachs(final int userId){
                      String sql = "SELECT post_id,post_attach FROM t_post “+
                      “where user_id =? and post_attach is not null ";
                      return getJdbcTemplate().query(
                      sql,new Object[] {userId},
                      new RowMapper() {
                      public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
                      int postId = rs.getInt(1);
                      ① 以二進(jìn)制數(shù)組方式獲取 BLOB 數(shù)據(jù)。
                      byte[] attach = lobHandler.getBlobAsBytes(rs, 2);
                      Post post = new Post();
                      post.setPostId(postId);
                      post.setPostAttach(attach);
                      return post;
                      }
                      });
                      }
                      

          通過 JdbcTemplate 的 List query(String sql, Object[] args, RowMapper rowMapper) 接口處理行數(shù)據(jù)的映射。在 RowMapper 回調(diào)的 mapRow() 接口方法中,通過 LobHandler 以 byte[] 獲取 BLOB 字段的數(shù)據(jù)。

          以流數(shù)據(jù)方式讀取 LOB 數(shù)據(jù)

          由于 LOB 數(shù)據(jù)可能很大(如 100M),如果直接以塊的方式操作 LOB 數(shù)據(jù),需要消耗大量的內(nèi)存資源,對應(yīng)用程序整體性能產(chǎn)生巨大的沖擊。對于體積很大的 LOB 數(shù)據(jù),我們可以使用流的方式進(jìn)行訪問,減少內(nèi)存的占用。JdbcTemplate 為此提供了一個(gè) Object query(String sql, Object[] args, ResultSetExtractor rse) 方法,ResultSetExtractor 接口擁有一個(gè)處理流數(shù)據(jù)的抽象類 org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor,可以通過擴(kuò)展此類用流的方式操作 LOB 字段的數(shù)據(jù)。下面我們?yōu)?PostJdbcDao 添加一個(gè)以流的方式獲取某個(gè)帖子附件的方法:


          清單 8. 以流方式訪問 LOB 數(shù)據(jù)
          …
                      public void getAttach(final int postId,final OutputStream os){ ① 用于接收 LOB 數(shù)據(jù)的輸出流
                      String sql = "SELECT post_attach FROM t_post WHERE post_id=? ";
                      getJdbcTemplate().query(
                      sql, new Object[] {postId},
                      new AbstractLobStreamingResultSetExtractor() { ② 匿名內(nèi)部類
                      ③ 處理未找到數(shù)據(jù)行的情況
                      protected void handleNoRowFound() throws LobRetrievalFailureException {
                      System.out.println("Not Found result!");
                      }
                      ④ 以流的方式處理 LOB 字段
                      public void streamData(ResultSet rs) throws SQLException, IOException {
                      InputStream is = lobHandler.getBlobAsBinaryStream(rs, 1);
                      if (is != null) {
                      FileCopyUtils.copy(is, os);
                      }
                      }
                      }
                      );
                      }
                      

          通過擴(kuò)展 AbstractLobStreamingResultSetExtractor 抽象類,在 streamData(ResultSet rs) 方法中以流的方式讀取 LOB 字段數(shù)據(jù),如 ④ 所示。這里我們又利用到了 Spring 的工具類 FileCopyUtils 將輸入流的數(shù)據(jù)拷貝到輸出流中。在 getAttach() 方法中通過入?yún)?OutputStream os 接收 LOB 的數(shù)據(jù),如 ① 所示。您可以同時(shí)覆蓋抽象類中的 handleNoRowFound() 方法,定義未找到數(shù)據(jù)行時(shí)的處理邏輯。





          回頁首


          在 JPA 中操作 LOB 數(shù)據(jù)

          在 JPA 中 LOB 類型的持久化更加簡單,僅需要通過特殊的 LOB 注釋(Annotation)就可以達(dá)到目的。我們對 Post 中的 LOB 屬性類型進(jìn)行注釋:


          清單 9. 注釋 LOB 類型屬性
          package com.baobaotao.domain;
                      …
                      import javax.persistence.Basic;
                      import javax.persistence.Lob;
                      import javax.persistence. Column;
                      @Entity(name = "T_POST")
                      public class Post implements Serializable {
                      …
                      @Lob ①-1 表示該屬性是 LOB 類型的字段
                      @Basic(fetch = FetchType.EAGER) ①-2 不采用延遲加載機(jī)制
                      @Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL") ①-3 對應(yīng)字段類型
                      private String postText;
                      @Lob ②-1 表示該屬性是 LOB 類型的字段
                      @Basic(fetch = FetchType. LAZY) ②-2 采用延遲加載機(jī)制
                      @Column(name = "POST_ATTACH", columnDefinition = "BLOB") ②-3 對應(yīng)字段類型
                      private byte[] postAttach;
                      …
                      }
                      

          postText 屬性對應(yīng) T_POST 表的 POST_TEXT 字段,該字段的類型是 LONTTEXT,并且非空。JPA 通過 @Lob 將屬性標(biāo)注為 LOB 類型,如 ①-1 和 ②-1 所示。通過 @Basic 指定 LOB 類型數(shù)據(jù)的獲取策略,FetchType.EAGER 表示非延遲加載,而 FetchType.LAZY 表示延遲加載,如 ①-2 和 ②-2 所示。通過 @ColumncolumnDefinition 屬性指定數(shù)據(jù)表對應(yīng)的 LOB 字段類型,如 ①-3 和 ②-3 所示。

          關(guān)于 JPA 注釋的更多信息,請閱讀 參考資源 中的相關(guān)技術(shù)文章。





          回頁首


          在 Hibernate 中操作 LOB 數(shù)據(jù)

          提示

          使用 Spring JDBC 時(shí),我們除了可以按 byte[]、String 類型處理 LOB 數(shù)據(jù)外,還可以使用流的方式操作 LOB 數(shù)據(jù),當(dāng) LOB 數(shù)據(jù)體積較大時(shí),流操作是唯一可行的方式。可惜,Spring 并未提供以流方式操作 LOB 數(shù)據(jù)的 UserType(記得 Spring 開發(fā)組成員認(rèn)為在實(shí)現(xiàn)上存在難度)。不過,www.atlassian.com 替 Spring 完成了這件難事,讀者可以通過 這里 了解到這個(gè)滿足要求的 BlobInputStream 類型。

          Hibernate 為處理特殊數(shù)據(jù)類型字段定義了一個(gè)接口:org.hibernate.usertype.UserType。Spring 在 org.springframework.orm.hibernate3.support 包中為 BLOB 和 CLOB 類型提供了幾個(gè) UserType 的實(shí)現(xiàn)類。因此,我們可以在 Hibernate 的映射文件中直接使用這兩個(gè)實(shí)現(xiàn)類輕松處理 LOB 類型的數(shù)據(jù)。

          • BlobByteArrayType:將 BLOB 數(shù)據(jù)映射為 byte[] 類型的屬性;
          • BlobStringType:將 BLOB 數(shù)據(jù)映射為 String 類型的屬性;
          • BlobSerializableType:將 BLOB 數(shù)據(jù)映射為 Serializable 類型的屬性;
          • ClobStringType:將 CLOB 數(shù)據(jù)映射為 String 類型的屬性;

          下面我們使用 Spring 的 UserType 為 Post 配置 Hibernate 的映射文件,如 清單 10 所示:


          清單 10 . LOB 數(shù)據(jù)映射配置
          <?xml version="1.0" encoding="UTF-8"?>
                      <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
                      "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
                      <hibernate-mapping auto-import="true" default-lazy="false">
                      <class name="com.baobaotao.domain.Post" table="t_post">
                      <id name="postId" column="post_id">
                      <generator class="identity" />
                      </id>
                      <property name="userId" column="user_id"/>
                      <property name="postText" column="post_text"
                      type="org.springframework.orm.hibernate3.support.ClobStringType"/>①對應(yīng) CLOB 字段
                      <property name="postAttach" column="post_attach"
                      type="org.springframework.orm.hibernate3.support.BlobByteArrayType"/>② BLOB 字段
                      <property name="postTime" column="post_time" type="date" />
                      <many-to-one name="topic" column="topic_id" class="com.baobaotao.domain.Topic" />
                      </class>
                      </hibernate-mapping>
                      

          postTextString 類型的屬性,對應(yīng)數(shù)據(jù)庫的 CLOB 類型,而 postAttachbyte[] 類型的屬性,對應(yīng)數(shù)據(jù)庫的 BLOB 類型。分別使用 Spring 所提供的相應(yīng) UserType 實(shí)現(xiàn)類進(jìn)行配置,如 ① 和 ② 處所示。

          在配置好映射文件后,還需要在 Spring 配置文件中定義 LOB 數(shù)據(jù)處理器,讓 SessionFactory 擁有處理 LOB 數(shù)據(jù)的能力:


          清單 11 . 將 LobHandler 注入到 SessionFactory 中
          …
                      <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
                      lazy-init="true" />
                      <bean id="sessionFactory"
                      class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
                      <property name="dataSource" ref="dataSource" />
                      <property name="lobHandler" ref="lobHandler" /> ① 設(shè)置 LOB 處理器
                      …
                      </bean>
                      

          在一般的數(shù)據(jù)庫(如 DB2)中,僅需要簡單地使用 HibernateTemplate#save(Object entity) 等方法就可以正確的保存 LOB 數(shù)據(jù)了。如果是 Oracle 9i 數(shù)據(jù)庫,還需要配置一個(gè)本地 JDBC 抽取器,并使用特定的 LobHandler 實(shí)現(xiàn)類,如 清單 4 所示。

          使用 LobHandler 操作 LOB 數(shù)據(jù)時(shí),需要在事務(wù)環(huán)境下才能工作,所以必須事先配置事務(wù)管理器,否則會拋出異常。





          回頁首


          在 iBatis 中操作 LOB 數(shù)據(jù)

          iBatis 為處理不同類型的數(shù)據(jù)定義了一個(gè)統(tǒng)一的接口:com.ibatis.sqlmap.engine.type.TypeHandler。這個(gè)接口類似于 Hibernate 的 UserType。iBatis 本身擁有該接口的眾多實(shí)現(xiàn)類,如 LongTypeHandler、DateTypeHandler 等,但沒有為 LOB 類型提供對應(yīng)的實(shí)現(xiàn)類。Spring 在 org.springframework.orm.ibatis.support 包中為我們提供了幾個(gè)處理 LOB 類型的 TypeHandler 實(shí)現(xiàn)類:

          • BlobByteArrayTypeHandler:將 BLOB 數(shù)據(jù)映射為 byte[] 類型;
          • BlobSerializableTypeHandler:將 BLOB 數(shù)據(jù)映射為 Serializable 類型的對象;
          • ClobStringTypeHandler:將 CLOB 數(shù)據(jù)映射為 String 類型;

          當(dāng)結(jié)果集中包括 LOB 數(shù)據(jù)時(shí),需要在結(jié)果集映射配置項(xiàng)中指定對應(yīng)的 Handler 類,下面我們采用 Spring 所提供的實(shí)現(xiàn)類對 Post 結(jié)果集的映射進(jìn)行配置。


          清單 12 . 對 LOB 數(shù)據(jù)進(jìn)行映射
          <?xml version="1.0" encoding="UTF-8" ?>
                      <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
                      "http://ibatis.apache.org/dtd/sql-map-2.dtd">
                      <sqlMap namespace="Post">
                      <typeAlias alias="post" type="com.baobaotao.domain.Post"/>
                      <resultMap id="result" class="post">
                      <result property="postId" column="post_id"/>
                      <result property="userId" column="user_id"/>
                      <result property="postText" column="post_text" ① 讀取 CLOB 類型數(shù)據(jù)
                      typeHandler="org.springframework.orm.ibatis.support.ClobStringTypeHandler"/>
                      <result property="postAttach" column="post_attach" ② 讀取 BLOB 類型數(shù)據(jù)
                      typeHandler="org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler"/>
                      </resultMap>
                      <select id="getPost" resultMap="result">
                      SELECT post_id,user_id,post_text,post_attach,post_time
                      FROM t_post  WHERE post_id =#postId#
                      </select>
                      <insert id="addPost">
                      INSERT INTO t_post(user_id,post_text,post_attach,post_time)
                      VALUES(#userId#,
                      #postText,handler=org.springframework.orm.ibatis.support.ClobStringTypeHandler#, ③
                      #postAttach,handler=org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler#, ④
                      #postTime#)
                      </insert>
                      </sqlMap>
                      

          提示

          為每一個(gè) LOB 類型字段分別指定處理器并不是一個(gè)好主意,iBatis 允許在 sql-map-config.xml 配置文件中通過 <typeHandler> 標(biāo)簽統(tǒng)一定義特殊類型數(shù)據(jù)的處理器,如:

          <typeHandler jdbcType="CLOB" javaType="java.lang.String" callback="org.springframework.orm.ibatis.support.ClobStringTypeHandler"/>

          當(dāng) iBatis 引擎從結(jié)果集中讀取或更改 LOB 類型數(shù)據(jù)時(shí),都需要指定處理器。我們在 ① 和 ② 處為讀取 LOB 類型的數(shù)據(jù)指定處理器,相似的,在 ③ 和 ④ 處為插入 LOB 類型的數(shù)據(jù)也指定處理器。

          此外,我們還必須為 SqlClientMap 提供一個(gè) LobHandler:


          清單 13. 將 LobHandler 注入到 SqlClientMap 中
          <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
                      lazy-init="true" />
                      <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
                      <property name="dataSource" ref="dataSource" />
                      <property name="lobHandler" ref="lobHandler" /> ①設(shè)置LobHandler
                      <property name="configLocation"
                      value="classpath:com/baobaotao/dao/ibatis/sql-map-config.xml" />
                      </bean>
                      

          處理 LOB 數(shù)據(jù)時(shí),Spring 要求在事務(wù)環(huán)境下工作,所以還必須配置一個(gè)事務(wù)管理器。iBatis 的事務(wù)管理器和 Spring JDBC 事務(wù)管理器相同,此處不再贅述。





          回頁首


          小結(jié)

          本文就 Spring 中如何操作 LOB 數(shù)據(jù)進(jìn)行較為全面的講解,您僅需簡單地配置 LobHandler 就可以直接在程序中象一般數(shù)據(jù)一樣操作 LOB 數(shù)據(jù)了。對于 ORM 框架來說,Spring 為它們分別提供了支持類,您僅要使用相應(yīng)的支持類進(jìn)行配置就可以了。因此您會發(fā)現(xiàn)在傳統(tǒng) JDBC 程序操作 LOB 頭疼的問題將變得輕松了許多。

          posted on 2008-10-27 11:31 蘆葦 閱讀(384) 評論(0)  編輯  收藏 所屬分類: Spring數(shù)據(jù)庫
          主站蜘蛛池模板: 黑水县| 米泉市| 当阳市| 佳木斯市| 滦平县| 亳州市| 津南区| 萨嘎县| 江华| 新余市| 精河县| 渭南市| 梁平县| 温宿县| 科技| 乌兰察布市| 普安县| 陇川县| 岑巩县| 华安县| 肥西县| 宁强县| 濮阳县| 连云港市| 寿阳县| 班戈县| 抚顺市| 凤冈县| 宝清县| 达州市| 辽阳县| 丰宁| 闻喜县| 潢川县| 中卫市| 南和县| 永年县| 白朗县| 平塘县| 澄城县| 大竹县|