本文講解了在 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 非常有個性地采用非 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)的實現(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ù)據(jù)源中返回的數(shù)據(jù)連接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類,這是因為數(shù)據(jù)源需要改變數(shù)據(jù)連接一些原有的行為以便對其進(jìn)行控制:如調(diào)用 Connection#close() 方法時,將數(shù)據(jù)連接返回到連接池中而非將其真的關(guān)閉。 在訪問 LOB 數(shù)據(jù)時,根據(jù)數(shù)據(jù)庫廠商的不同,可能需要使用被代理前的本地 JDBC 對象(如 DB2Connection 或 DB2ResultSet)特有的 API。為了從數(shù)據(jù)源中獲取本地 JDBC 對象, Spring 定義了 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 接口并提供了相應(yīng)的實現(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)行代理,這時可以直接使用 SimpleNativeJdbcExtractor 實現(xiàn)類。但有些數(shù)據(jù)源(如 Jakarta Commons DBCP)會對所有的 JDBC 對象進(jìn)行代理,這時,就需要根據(jù)具體的情況選擇適合的抽取器實現(xiàn)類了。下表列出了不同數(shù)據(jù)源本地 JDBC 對象抽取器的實現(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 的本地數(shù)據(jù)庫連接 DB2Connection 的方法: 清單 1. 獲取本地數(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; ③ 這時可以強(qiáng)制進(jìn)行類型轉(zhuǎn)換了 … } catch (Exception e) { e.printStackTrace(); } } } | 在 ① 處我們通過 DataSourceUtils 獲取當(dāng)前線程綁定的數(shù)據(jù)連接,為了使用線程上下文相關(guān)的事務(wù),通過 DataSourceUtils 從數(shù)據(jù)源中獲取連接是正確的做法,如果直接通過 dateSource 獲取連接,則將得到一個和當(dāng)前線程上下文無關(guān)的數(shù)據(jù)連接實例。 JdbcTemplate 可以在配置時注入一個本地 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 實例后,我們就可以使用該對象的一些特有功能了,如使用 DB2Connection 的特殊 API 對 LOB 對象進(jìn)行操作。 LobCreator 雖然 JDBC 定義了兩個操作 LOB 類型的接口:java.sql.Blob 和 java.sql.Clob ,但有些廠商的 JDBC 驅(qū)動程序并不支持這兩個接口。為此,Spring 定義了一個獨(dú)立于 java.sql.Blob/Clob 的 LobCreator 接口,以統(tǒng)一的方式操作各種數(shù)據(jù)庫的 LOB 類型數(shù)據(jù)。因為 LobCreator 本身持有 LOB 所對應(yīng)的數(shù)據(jù)庫資源,所以它不是線程安全的,一個 LobCreator 只能操作一個 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 實現(xiàn)類,而其它的數(shù)據(jù)庫統(tǒng)一使用 DefaultLobHandler 就可以了。Oracle 10g 改正了 Oracle 9i 這個異化的風(fēng)格,終于天下歸一了,所以 Oracle 10g 也可以使用 DefaultLobHandler 。 下面,我們來看一下 LobHandler 接口的幾個重要方法: 方法 | 說明 | 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() | 生成一個會話相關(guān)的 LobCreator 對象 |
在 Spring JDBC 中操作 LOB 數(shù)據(jù) 插入 LOB 數(shù)據(jù) 假設(shè)我們有一個用于保存論壇帖子的 t_post 表,擁有兩個 LOB 字段,其中 post_text 是 CLOB 類型,而 post_attach 是 BLOB 類型。下面,我們來編寫插入一個帖子記錄的代碼: 清單 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 中引入了一個 LobHandler 屬性,如 ① 所示,并通過 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 方法完成插入 LOB 數(shù)據(jù)的操作。我們通過匿名內(nèi)部類的方式定義 LobCreatingPreparedStatementCallback 抽象類的子類,其構(gòu)造函數(shù)需要一個 LobHandler 入?yún)?,?② 所示。在匿名類中實現(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)注意到 nativeJdbcExtractor 和 oracleLobHandler Bean 都設(shè)置為 lazy-init="true" ,這是因為 nativeJdbcExtractor 需要通過運(yùn)行期的反射機(jī)制獲取底層的 JDBC 對象,所以需要避免在 Spring 容器啟動時就實例化這兩個 Bean。 LobHandler 需要訪問本地 JDBC 對象,這一任務(wù)委托給 NativeJdbcExtractor Bean 來完成,因此我們在 ① 處為 LobHandler 注入了一個 nativeJdbcExtractor 。最后,我們把 lobHandler Bean 注入到需要進(jìn)行 LOB 數(shù)據(jù)訪問操作的 PostJdbcDao 中,如 ② 所示。 如果底層數(shù)據(jù)庫是 DB2、SQL Server、MySQL 等非 Oracle 的其它數(shù)據(jù)庫,則只要簡單配置一個 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); } } | 這里,有幾個知識點需要稍微解釋一下:AbstractDependencyInjectionSpringContextTests 是 Spring 專門為測試提供的類,它能夠直接從 IoC 容器中裝載 Bean。此外,我們使用了 ClassPathResource 加載圖片資源,并通過 FileCopyUtils 讀取文件的數(shù)據(jù)。ClassPathResource 和 FileCopyUtils 都是 Spring 提供的非常實用的工具類。 以塊數(shù)據(jù)方式讀取 LOB 數(shù)據(jù) 您可以直接用數(shù)據(jù)塊的方式讀取 LOB 數(shù)據(jù):用 String 讀取 CLOB 字段的數(shù)據(jù),用 byte[] 讀取 BLOB 字段的數(shù)據(jù)。在 PostJdbcDao 中添加一個 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 為此提供了一個 Object query(String sql, Object[] args, ResultSetExtractor rse) 方法,ResultSetExtractor 接口擁有一個處理流數(shù)據(jù)的抽象類 org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor ,可以通過擴(kuò)展此類用流的方式操作 LOB 字段的數(shù)據(jù)。下面我們?yōu)?PostJdbcDao 添加一個以流的方式獲取某個帖子附件的方法: 清單 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ù),如 ① 所示。您可以同時覆蓋抽象類中的 handleNoRowFound() 方法,定義未找到數(shù)據(jù)行時的處理邏輯。
在 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 所示。通過 @Column 的 columnDefinition 屬性指定數(shù)據(jù)表對應(yīng)的 LOB 字段類型,如 ①-3 和 ②-3 所示。 關(guān)于 JPA 注釋的更多信息,請閱讀 參考資源 中的相關(guān)技術(shù)文章。
在 Hibernate 中操作 LOB 數(shù)據(jù) | 提示 使用 Spring JDBC 時,我們除了可以按 byte[]、String 類型處理 LOB 數(shù)據(jù)外,還可以使用流的方式操作 LOB 數(shù)據(jù),當(dāng) LOB 數(shù)據(jù)體積較大時,流操作是唯一可行的方式。可惜,Spring 并未提供以流方式操作 LOB 數(shù)據(jù)的 UserType(記得 Spring 開發(fā)組成員認(rèn)為在實現(xiàn)上存在難度)。不過,www.atlassian.com 替 Spring 完成了這件難事,讀者可以通過 這里 了解到這個滿足要求的 BlobInputStream 類型。 | | Hibernate 為處理特殊數(shù)據(jù)類型字段定義了一個接口:org.hibernate.usertype.UserType 。Spring 在 org.springframework.orm.hibernate3.support 包中為 BLOB 和 CLOB 類型提供了幾個 UserType 的實現(xiàn)類。因此,我們可以在 Hibernate 的映射文件中直接使用這兩個實現(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> | postText 為 String 類型的屬性,對應(yīng)數(shù)據(jù)庫的 CLOB 類型,而 postAttach 為 byte[] 類型的屬性,對應(yīng)數(shù)據(jù)庫的 BLOB 類型。分別使用 Spring 所提供的相應(yīng) UserType 實現(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ù)庫,還需要配置一個本地 JDBC 抽取器,并使用特定的 LobHandler 實現(xiàn)類,如 清單 4 所示。 使用 LobHandler 操作 LOB 數(shù)據(jù)時,需要在事務(wù)環(huán)境下才能工作,所以必須事先配置事務(wù)管理器,否則會拋出異常。
在 iBatis 中操作 LOB 數(shù)據(jù) iBatis 為處理不同類型的數(shù)據(jù)定義了一個統(tǒng)一的接口:com.ibatis.sqlmap.engine.type.TypeHandler 。這個接口類似于 Hibernate 的 UserType。iBatis 本身擁有該接口的眾多實現(xiàn)類,如 LongTypeHandler、DateTypeHandler 等,但沒有為 LOB 類型提供對應(yīng)的實現(xiàn)類。Spring 在 org.springframework.orm.ibatis.support 包中為我們提供了幾個處理 LOB 類型的 TypeHandler 實現(xiàn)類: BlobByteArrayTypeHandler :將 BLOB 數(shù)據(jù)映射為 byte[] 類型; BlobSerializableTypeHandler :將 BLOB 數(shù)據(jù)映射為 Serializable 類型的對象; ClobStringTypeHandler :將 CLOB 數(shù)據(jù)映射為 String 類型; 當(dāng)結(jié)果集中包括 LOB 數(shù)據(jù)時,需要在結(jié)果集映射配置項中指定對應(yīng)的 Handler 類,下面我們采用 Spring 所提供的實現(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> | | 提示 為每一個 LOB 類型字段分別指定處理器并不是一個好主意,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ù)時,都需要指定處理器。我們在 ① 和 ② 處為讀取 LOB 類型的數(shù)據(jù)指定處理器,相似的,在 ③ 和 ④ 處為插入 LOB 類型的數(shù)據(jù)也指定處理器。 此外,我們還必須為 SqlClientMap 提供一個 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ù)時,Spring 要求在事務(wù)環(huán)境下工作,所以還必須配置一個事務(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 頭疼的問題將變得輕松了許多。 |