本文講解了在 Spring 中處理 LOB 數據的原理和方法,對于 Spring JDBC 以及 Spring 所集成的第三方 ORM 框架(包括 JPA、Hibernate 和 iBatis)如何處理 LOB 數據進(jìn)行了闡述。 概述 LOB 代表大對象數據,包括 BLOB 和 CLOB 兩種類(lèi)型,前者用于存儲大塊的二進(jìn)制數據,如圖片數據,視頻數據等,而后者用于存儲長(cháng)文本數據,如論壇的帖子內容,產(chǎn)品的詳細描述等。值得注意的是:在不同的數據庫中,大對象對應的字段類(lèi)型是不盡相同的,如 DB2 對應 BLOB/CLOB,MySql 對應 BLOB/LONGTEXT,SqlServer 對應 IMAGE/TEXT。需要指出的是,有些數據庫的大對象類(lèi)型可以象簡(jiǎn)單類(lèi)型一樣訪(fǎng)問(wèn),如 MySql 的 LONGTEXT 的操作方式和 VARCHAR 類(lèi)型一樣。在一般情況下, LOB 類(lèi)型數據的訪(fǎng)問(wèn)方式不同于其它簡(jiǎn)單類(lèi)型的數據,我們經(jīng)常會(huì )以流的方式操作 LOB 類(lèi)型的數據。此外,LOB 類(lèi)型數據的訪(fǎng)問(wèn)不是線(xiàn)程安全的,需要為其單獨分配相應的數據庫資源,并在操作完成后釋放資源。最后,Oracle 9i 非常有個(gè)性地采用非 JDBC 標準的 API 操作 LOB 數據。所有這些情況給編寫(xiě)操作 LOB 類(lèi)型數據的程序帶來(lái)挑戰,Spring 在 org.springframework.jdbc.support.lob 包中為我們提供了相應的幫助類(lèi),以便我們輕松應對這頭攔路虎。 Spring 大大降低了我們處理 LOB 數據的難度。首先,Spring 提供了 NativeJdbcExtractor 接口,您可以在不同環(huán)境里選擇相應的實(shí)現類(lèi)從數據源中獲取本地 JDBC 對象;其次,Spring 通過(guò) LobCreator 接口取消了不同數據廠(chǎng)商操作 LOB 數據的差別,并提供了創(chuàng )建 LobCreator 的 LobHandler 接口,您只要根據底層數據庫類(lèi)型選擇合適的 LobHandler 進(jìn)行配置即可。 本文將詳細地講述通過(guò) Spring JDBC 插入和訪(fǎng)問(wèn) LOB 數據的具體過(guò)程。不管是以塊的方式還是以流的方式,您都可以通過(guò) LobCreator 和 LobHandler 方便地訪(fǎng)問(wèn) LOB 數據。對于 ORM 框架來(lái)說(shuō),JPA 擁有自身處理 LOB 數據的配置類(lèi)型,Spring 為 Hibernate 和 iBatis 分別提供了 LOB 數據類(lèi)型的配置類(lèi),您僅需要使用這些類(lèi)進(jìn)行簡(jiǎn)單的配置就可以像普通類(lèi)型一樣操作 LOB 類(lèi)型數據。
本地 JDBC 對象 當您在 Web 應用服務(wù)器或 Spring 中配置數據源時(shí),從數據源中返回的數據連接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類(lèi),這是因為數據源需要改變數據連接一些原有的行為以便對其進(jìn)行控制:如調用 Connection#close() 方法時(shí),將數據連接返回到連接池中而非將其真的關(guān)閉。 在訪(fǎng)問(wèn) LOB 數據時(shí),根據數據庫廠(chǎng)商的不同,可能需要使用被代理前的本地 JDBC 對象(如 DB2Connection 或 DB2ResultSet)特有的 API。為了從數據源中獲取本地 JDBC 對象, Spring 定義了 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 接口并提供了相應的實(shí)現類(lèi)。NativeJdbcExtractor 定義了從數據源中抽取本地 JDBC 對象的若干方法: | 方法 | 說(shuō)明 | 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 對象 | 有些簡(jiǎn)單的數據源僅對 Connection 對象進(jìn)行代理,這時(shí)可以直接使用 SimpleNativeJdbcExtractor 實(shí)現類(lèi)。但有些數據源(如 Jakarta Commons DBCP)會(huì )對所有的 JDBC 對象進(jìn)行代理,這時(shí),就需要根據具體的情況選擇適合的抽取器實(shí)現類(lèi)了。下表列出了不同數據源本地 JDBC 對象抽取器的實(shí)現類(lèi): | 數據源類(lèi)型 | 說(shuō)明 | | WebSphere 4 及以上版本的數據源 | org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor | | WebLogic 6.1+ 及以上版本的數據源 | org.springframework.jdbc.support.nativejdbc.WebLogicNativeJdbcExtractor | | JBoss 3.2.4 及以上版本的數據源 | org.springframework.jdbc.support.nativejdbc.JBossNativeJdbcExtractor | | C3P0 數據源 | org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor | | DBCP 數據源 | org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor | | ObjectWeb 的 XAPool 數據源 | org.springframework.jdbc.support.nativejdbc.XAPoolNativeJdbcExtractor | 下面的代碼演示了從 DBCP 數據源中獲取 DB2 的本地數據庫連接 DB2Connection 的方法: 清單 1. 獲取本地數據庫連接 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 從模板類(lèi)中獲取連接 ② 使用模板類(lèi)的本地 JDBC 抽取器獲取本地的 Connection conn = getJdbcTemplate().getNativeJdbcExtractor().getNativeConnection(conn); DB2Connection db2conn = (DB2Connection) conn; ③ 這時(shí)可以強制進(jìn)行類(lèi)型轉換了 … } catch (Exception e) { e.printStackTrace(); } } } | 在 ① 處我們通過(guò) DataSourceUtils 獲取當前線(xiàn)程綁定的數據連接,為了使用線(xiàn)程上下文相關(guān)的事務(wù),通過(guò) DataSourceUtils 從數據源中獲取連接是正確的做法,如果直接通過(guò) dateSource 獲取連接,則將得到一個(gè)和當前線(xiàn)程上下文無(wú)關(guān)的數據連接實(shí)例。 JdbcTemplate 可以在配置時(shí)注入一個(gè)本地 JDBC 對象抽取器,要使代碼 清單 1 正確運行,我們必須進(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 數據源的 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" /> ② 設置抽取器 <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 類(lèi)型的接口:java.sql.Blob 和 java.sql.Clob,但有些廠(chǎng)商的 JDBC 驅動(dòng)程序并不支持這兩個(gè)接口。為此,Spring 定義了一個(gè)獨立于 java.sql.Blob/Clob 的 LobCreator 接口,以統一的方式操作各種數據庫的 LOB 類(lèi)型數據。因為 LobCreator 本身持有 LOB 所對應的數據庫資源,所以它不是線(xiàn)程安全的,一個(gè) LobCreator 只能操作一個(gè) LOB 數據。 為了方便在 PreparedStatement 中使用 LobCreator,您可以直接使用 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 方法。下面對 LobCreator 接口中的方法進(jìn)行簡(jiǎn)要說(shuō)明: | 方法 | 說(shuō)明 | void close() | 關(guān)閉會(huì )話(huà),并釋放 LOB 資源 | void setBlobAsBinaryStream(PreparedStatement ps, int paramIndex, InputStream contentStream, int contentLength) | 通過(guò)流填充 BLOB 數據 | void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) | 通過(guò)二進(jìn)制數據填充 BLOB 數據 | void setClobAsAsciiStream(PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) | 通過(guò) Ascii 字符流填充 CLOB 數據 | void setClobAsCharacterStream(PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength) | 通過(guò) Unicode 字符流填充 CLOB 數據 | void setClobAsString(PreparedStatement ps, int paramIndex, String content) | 通過(guò)字符串填充 CLOB 數據 | LobHandler LobHandler 接口為操作 BLOB/CLOB 提供了統一訪(fǎng)問(wèn)接口,而不管底層數據庫究竟是以大對象的方式還是以一般數據類(lèi)型的方式進(jìn)行操作。此外,LobHandler 還充當了 LobCreator 的工廠(chǎng)類(lèi)。 大部分數據庫廠(chǎng)商的 JDBC 驅動(dòng)程序(如 DB2)都以 JDBC 標準的 API 操作 LOB 數據,但 Oracle 9i 及以前的 JDBC 驅動(dòng)程序采用了自己的 API 操作 LOB 數據,Oracle 9i 直接使用自己的 API 操作 LOB 數據,且不允許通過(guò) PreparedStatement 的 setAsciiStream()、setBinaryStream()、setCharacterStream() 等方法填充流數據。Spring 提供 LobHandler 接口主要是為了遷就 Oracle 特立獨行的作風(fēng)。所以 Oracle 必須使用 OracleLobHandler 實(shí)現類(lèi),而其它的數據庫統一使用 DefaultLobHandler 就可以了。Oracle 10g 改正了 Oracle 9i 這個(gè)異化的風(fēng)格,終于天下歸一了,所以 Oracle 10g 也可以使用 DefaultLobHandler。 下面,我們來(lái)看一下 LobHandler 接口的幾個(gè)重要方法: | 方法 | 說(shuō)明 | InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) | 從結果集中返回 InputStream,通過(guò) InputStream 讀取 BLOB 數據 | byte[] getBlobAsBytes(ResultSet rs, int columnIndex) | 以二進(jìn)制數據的方式獲取結果集中的 BLOB 數據; | InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) | 從結果集中返回 InputStream,通過(guò) InputStreamn 以 Ascii 字符流方式讀取 BLOB 數據 | Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) | 從結果集中獲取 Unicode 字符流 Reader,并通過(guò) Reader以Unicode 字符流方式讀取 CLOB 數據 | String getClobAsString(ResultSet rs, int columnIndex) | 從結果集中以字符串的方式獲取 CLOB 數據 | LobCreator getLobCreator() | 生成一個(gè)會(huì )話(huà)相關(guān)的 LobCreator 對象 |
在 Spring JDBC 中操作 LOB 數據 插入 LOB 數據 假設我們有一個(gè)用于保存論壇帖子的 t_post 表,擁有兩個(gè) LOB 字段,其中 post_text 是 CLOB 類(lèi)型,而 post_attach 是 BLOB 類(lèi)型。下面,我們來(lái)編寫(xiě)插入一個(gè)帖子記錄的代碼: 清單 3. 添加 LOB 字段數據 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()); ③ 設置 CLOB 字段 lobCreator.setClobAsString(ps, 3, post.getPostText()); ④ 設置 BLOB 字段 lobCreator.setBlobAsBytes(ps, 4, post.getPostAttach()); } }); } … } | 首先,我們在 PostJdbcDao 中引入了一個(gè) LobHandler 屬性,如 ① 所示,并通過(guò) JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 方法完成插入 LOB 數據的操作。我們通過(guò)匿名內部類(lèi)的方式定義 LobCreatingPreparedStatementCallback 抽象類(lèi)的子類(lèi),其構造函數需要一個(gè) LobHandler 入參,如 ② 所示。在匿名類(lèi)中實(shí)現了父類(lèi)的抽象方法 setValues(PreparedStatement ps,LobCreator lobCreator),在該方法中通過(guò) lobCreator 操作 LOB 對象,如 ③、④ 所示,我們分別通過(guò)字符串和二進(jìn)制數組填充 BLOB 和 CLOB 的數據。您同樣可以使用流的方式填充 LOB 數據,僅需要調用 lobCreator 相應的流填充方法即可。 我們需要調整 Spring 的配置文件以配合我們剛剛定義的 PostJdbcDao。假設底層數據庫是 Oracle,可以采用以下的配置方式: 清單 4. Oracle 數據庫的 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"/> ① 設置本地 Jdbc 對象抽取器 </bean> <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao"> <property name="lobHandler" ref="oracleLobHandler"/> ② 設置 LOB 處理器 </bean> | 大家可能已經(jīng)注意到 nativeJdbcExtractor 和 oracleLobHandler Bean 都設置為 lazy-init="true",這是因為 nativeJdbcExtractor 需要通過(guò)運行期的反射機制獲取底層的 JDBC 對象,所以需要避免在 Spring 容器啟動(dòng)時(shí)就實(shí)例化這兩個(gè) Bean。 LobHandler 需要訪(fǎng)問(wèn)本地 JDBC 對象,這一任務(wù)委托給 NativeJdbcExtractor Bean 來(lái)完成,因此我們在 ① 處為 LobHandler 注入了一個(gè) nativeJdbcExtractor。最后,我們把 lobHandler Bean 注入到需要進(jìn)行 LOB 數據訪(fǎng)問(wèn)操作的 PostJdbcDao 中,如 ② 所示。 如果底層數據庫是 DB2、SQL Server、MySQL 等非 Oracle 的其它數據庫,則只要簡(jiǎn)單配置一個(gè) DefaultLobHandler 就可以了,如下所示: 清單 5. 一般數據庫 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 只是簡(jiǎn)單地代理標準 JDBC 的 PreparedStatement 和 ResultSet 對象,由于并不需要訪(fǎng)問(wèn)數據庫驅動(dòng)本地的 JDBC 對象,所以它不需要 NativeJdbcExtractor 的幫助。您可以通過(guò)以下的代碼測試 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()); ② 讀取圖片文件的數據 post.setPostAttach(mockImg); post.setPostText("測試帖子的內容"); postDao.addPost(post); } } | 這里,有幾個(gè)知識點(diǎn)需要稍微解釋一下:AbstractDependencyInjectionSpringContextTests 是 Spring 專(zhuān)門(mén)為測試提供的類(lèi),它能夠直接從 IoC 容器中裝載 Bean。此外,我們使用了 ClassPathResource 加載圖片資源,并通過(guò) FileCopyUtils 讀取文件的數據。ClassPathResource 和 FileCopyUtils 都是 Spring 提供的非常實(shí)用的工具類(lèi)。 以塊數據方式讀取 LOB 數據 您可以直接用數據塊的方式讀取 LOB 數據:用 String 讀取 CLOB 字段的數據,用 byte[] 讀取 BLOB 字段的數據。在 PostJdbcDao 中添加一個(gè) getAttachs() 方法,以便獲取某一用戶(hù)的所有帶附件的帖子: 清單 7. 以塊數據訪(fǎng)問(wèn) LOB 數據 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)制數組方式獲取 BLOB 數據。 byte[] attach = lobHandler.getBlobAsBytes(rs, 2); Post post = new Post(); post.setPostId(postId); post.setPostAttach(attach); return post; } }); } | 通過(guò) JdbcTemplate 的 List query(String sql, Object[] args, RowMapper rowMapper) 接口處理行數據的映射。在 RowMapper 回調的 mapRow() 接口方法中,通過(guò) LobHandler 以 byte[] 獲取 BLOB 字段的數據。 以流數據方式讀取 LOB 數據 由于 LOB 數據可能很大(如 100M),如果直接以塊的方式操作 LOB 數據,需要消耗大量的內存資源,對應用程序整體性能產(chǎn)生巨大的沖擊。對于體積很大的 LOB 數據,我們可以使用流的方式進(jìn)行訪(fǎng)問(wèn),減少內存的占用。JdbcTemplate 為此提供了一個(gè) Object query(String sql, Object[] args, ResultSetExtractor rse) 方法,ResultSetExtractor 接口擁有一個(gè)處理流數據的抽象類(lèi) org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor,可以通過(guò)擴展此類(lèi)用流的方式操作 LOB 字段的數據。下面我們?yōu)?PostJdbcDao 添加一個(gè)以流的方式獲取某個(gè)帖子附件的方法: 清單 8. 以流方式訪(fǎng)問(wèn) LOB 數據 … public void getAttach(final int postId,final OutputStream os){ ① 用于接收 LOB 數據的輸出流 String sql = "SELECT post_attach FROM t_post WHERE post_id=? "; getJdbcTemplate().query( sql, new Object[] {postId}, new AbstractLobStreamingResultSetExtractor() { ② 匿名內部類(lèi) ③ 處理未找到數據行的情況 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); } } } ); } | 通過(guò)擴展 AbstractLobStreamingResultSetExtractor 抽象類(lèi),在 streamData(ResultSet rs) 方法中以流的方式讀取 LOB 字段數據,如 ④ 所示。這里我們又利用到了 Spring 的工具類(lèi) FileCopyUtils 將輸入流的數據拷貝到輸出流中。在 getAttach() 方法中通過(guò)入參 OutputStream os 接收 LOB 的數據,如 ① 所示。您可以同時(shí)覆蓋抽象類(lèi)中的 handleNoRowFound() 方法,定義未找到數據行時(shí)的處理邏輯。
在 JPA 中操作 LOB 數據 在 JPA 中 LOB 類(lèi)型的持久化更加簡(jiǎn)單,僅需要通過(guò)特殊的 LOB 注釋?zhuān)ˋnnotation)就可以達到目的。我們對 Post 中的 LOB 屬性類(lèi)型進(jìn)行注釋?zhuān)?/p> 清單 9. 注釋 LOB 類(lèi)型屬性 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 類(lèi)型的字段 @Basic(fetch = FetchType.EAGER) ①-2 不采用延遲加載機制 @Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL") ①-3 對應字段類(lèi)型 private String postText; @Lob ②-1 表示該屬性是 LOB 類(lèi)型的字段 @Basic(fetch = FetchType. LAZY) ②-2 采用延遲加載機制 @Column(name = "POST_ATTACH", columnDefinition = "BLOB") ②-3 對應字段類(lèi)型 private byte[] postAttach; … } | postText 屬性對應 T_POST 表的 POST_TEXT 字段,該字段的類(lèi)型是 LONTTEXT,并且非空。JPA 通過(guò) @Lob 將屬性標注為 LOB 類(lèi)型,如 ①-1 和 ②-1 所示。通過(guò) @Basic 指定 LOB 類(lèi)型數據的獲取策略,FetchType.EAGER 表示非延遲加載,而 FetchType.LAZY 表示延遲加載,如 ①-2 和 ②-2 所示。通過(guò) @Column 的 columnDefinition 屬性指定數據表對應的 LOB 字段類(lèi)型,如 ①-3 和 ②-3 所示。 關(guān)于 JPA 注釋的更多信息,請閱讀 參考資源 中的相關(guān)技術(shù)文章。
在 Hibernate 中操作 LOB 數據 | 提示 使用 Spring JDBC 時(shí),我們除了可以按 byte[]、String 類(lèi)型處理 LOB 數據外,還可以使用流的方式操作 LOB 數據,當 LOB 數據體積較大時(shí),流操作是唯一可行的方式??上?,Spring 并未提供以流方式操作 LOB 數據的 UserType(記得 Spring 開(kāi)發(fā)組成員認為在實(shí)現上存在難度)。不過(guò),www.atlassian.com 替 Spring 完成了這件難事,讀者可以通過(guò) 這里 了解到這個(gè)滿(mǎn)足要求的 BlobInputStream 類(lèi)型。 | | Hibernate 為處理特殊數據類(lèi)型字段定義了一個(gè)接口:org.hibernate.usertype.UserType。Spring 在 org.springframework.orm.hibernate3.support 包中為 BLOB 和 CLOB 類(lèi)型提供了幾個(gè) UserType 的實(shí)現類(lèi)。因此,我們可以在 Hibernate 的映射文件中直接使用這兩個(gè)實(shí)現類(lèi)輕松處理 LOB 類(lèi)型的數據。 BlobByteArrayType:將 BLOB 數據映射為 byte[] 類(lèi)型的屬性; BlobStringType:將 BLOB 數據映射為 String 類(lèi)型的屬性; BlobSerializableType:將 BLOB 數據映射為 Serializable 類(lèi)型的屬性; ClobStringType:將 CLOB 數據映射為 String 類(lèi)型的屬性; 下面我們使用 Spring 的 UserType 為 Post 配置 Hibernate 的映射文件,如 清單 10 所示: 清單 10 . LOB 數據映射配置 <?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"/>①對應 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> | postText 為 String 類(lèi)型的屬性,對應數據庫的 CLOB 類(lèi)型,而 postAttach 為 byte[] 類(lèi)型的屬性,對應數據庫的 BLOB 類(lèi)型。分別使用 Spring 所提供的相應 UserType 實(shí)現類(lèi)進(jìn)行配置,如 ① 和 ② 處所示。 在配置好映射文件后,還需要在 Spring 配置文件中定義 LOB 數據處理器,讓 SessionFactory 擁有處理 LOB 數據的能力: 清單 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" /> ① 設置 LOB 處理器 … </bean> | 在一般的數據庫(如 DB2)中,僅需要簡(jiǎn)單地使用 HibernateTemplate#save(Object entity) 等方法就可以正確的保存 LOB 數據了。如果是 Oracle 9i 數據庫,還需要配置一個(gè)本地 JDBC 抽取器,并使用特定的 LobHandler 實(shí)現類(lèi),如 清單 4 所示。 使用 LobHandler 操作 LOB 數據時(shí),需要在事務(wù)環(huán)境下才能工作,所以必須事先配置事務(wù)管理器,否則會(huì )拋出異常。
在 iBatis 中操作 LOB 數據 iBatis 為處理不同類(lèi)型的數據定義了一個(gè)統一的接口:com.ibatis.sqlmap.engine.type.TypeHandler。這個(gè)接口類(lèi)似于 Hibernate 的 UserType。iBatis 本身?yè)碛性摻涌诘谋姸鄬?shí)現類(lèi),如 LongTypeHandler、DateTypeHandler 等,但沒(méi)有為 LOB 類(lèi)型提供對應的實(shí)現類(lèi)。Spring 在 org.springframework.orm.ibatis.support 包中為我們提供了幾個(gè)處理 LOB 類(lèi)型的 TypeHandler 實(shí)現類(lèi): BlobByteArrayTypeHandler:將 BLOB 數據映射為 byte[] 類(lèi)型; BlobSerializableTypeHandler:將 BLOB 數據映射為 Serializable 類(lèi)型的對象; ClobStringTypeHandler:將 CLOB 數據映射為 String 類(lèi)型; 當結果集中包括 LOB 數據時(shí),需要在結果集映射配置項中指定對應的 Handler 類(lèi),下面我們采用 Spring 所提供的實(shí)現類(lèi)對 Post 結果集的映射進(jìn)行配置。 清單 12 . 對 LOB 數據進(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 類(lèi)型數據 typeHandler="org.springframework.orm.ibatis.support.ClobStringTypeHandler"/> <result property="postAttach" column="post_attach" ② 讀取 BLOB 類(lèi)型數據 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 類(lèi)型字段分別指定處理器并不是一個(gè)好主意,iBatis 允許在 sql-map-config.xml 配置文件中通過(guò) <typeHandler> 標簽統一定義特殊類(lèi)型數據的處理器,如: <typeHandler jdbcType="CLOB" javaType="java.lang.String" callback="org.springframework.orm.ibatis.support.ClobStringTypeHandler"/> | | 當 iBatis 引擎從結果集中讀取或更改 LOB 類(lèi)型數據時(shí),都需要指定處理器。我們在 ① 和 ② 處為讀取 LOB 類(lèi)型的數據指定處理器,相似的,在 ③ 和 ④ 處為插入 LOB 類(lèi)型的數據也指定處理器。 此外,我們還必須為 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" /> ①設置LobHandler <property name="configLocation" value="classpath:com/baobaotao/dao/ibatis/sql-map-config.xml" /> </bean> | 處理 LOB 數據時(shí),Spring 要求在事務(wù)環(huán)境下工作,所以還必須配置一個(gè)事務(wù)管理器。iBatis 的事務(wù)管理器和 Spring JDBC 事務(wù)管理器相同,此處不再贅述。
小結 本文就 Spring 中如何操作 LOB 數據進(jìn)行較為全面的講解,您僅需簡(jiǎn)單地配置 LobHandler 就可以直接在程序中象一般數據一樣操作 LOB 數據了。對于 ORM 框架來(lái)說(shuō),Spring 為它們分別提供了支持類(lèi),您僅要使用相應的支持類(lèi)進(jìn)行配置就可以了。因此您會(huì )發(fā)現在傳統 JDBC 程序操作 LOB 頭疼的問(wèn)題將變得輕松了許多。 |