本文講解了在 Spring 中處理 LOB 數(shù)據(jù)的原理和方法,對于 Spring JDBC 以及 Spring 所集成的第三方 ORM 框架(包括 JPA、Hibernate 和 iBatis)如何處理 LOB 數(shù)據(jù)進行了闡述。
LOB 代表大對象數(shù)據(jù),包括 BLOB 和 CLOB 兩種類型,前者用于存儲大塊的二進制數(shù)據(jù),如圖片數(shù)據(jù),視頻數(shù)據(jù)等,而后者用于存儲長文本數(shù)據(jù),如論壇的帖子內(nèi)容,產(chǎn)品的詳細描述等。值得注意的是:在不同的數(shù)據(jù)庫中,大對象對應的字段類型是不盡相同的,如 DB2 對應 BLOB/CLOB,MySql 對應 BLOB/LONGTEXT,SqlServer 對應 IMAGE/TEXT。需要指出的是,有些數(shù)據(jù)庫的大對象類型可以象簡單類型一樣訪問,如 MySql 的 LONGTEXT 的操作方式和 VARCHAR 類型一樣。在一般情況下, LOB 類型數(shù)據(jù)的訪問方式不同于其它簡單類型的數(shù)據(jù),我們經(jīng)常會以流的方式操作 LOB 類型的數(shù)據(jù)。此外,LOB 類型數(shù)據(jù)的訪問不是線程安全的,需要為其單獨分配相應的數(shù)據(jù)庫資源,并在操作完成后釋放資源。最后,Oracle 9i 非常有個性地采用非 JDBC 標準的 API 操作 LOB 數(shù)據(jù)。所有這些情況給編寫操作 LOB 類型數(shù)據(jù)的程序帶來挑戰(zhàn),Spring 在 org.springframework.jdbc.support.lob
包中為我們提供了相應的幫助類,以便我們輕松應對這頭攔路虎。
Spring 大大降低了我們處理 LOB 數(shù)據(jù)的難度。首先,Spring 提供了 NativeJdbcExtractor
接口,您可以在不同環(huán)境里選擇相應的實現(xiàn)類從數(shù)據(jù)源中獲取本地 JDBC 對象;其次,Spring 通過 LobCreator
接口取消了不同數(shù)據(jù)廠商操作 LOB 數(shù)據(jù)的差別,并提供了創(chuàng)建 LobCreator 的 LobHandler
接口,您只要根據(jù)底層數(shù)據(jù)庫類型選擇合適的 LobHandler 進行配置即可。
本文將詳細地講述通過 Spring JDBC 插入和訪問 LOB 數(shù)據(jù)的具體過程。不管是以塊的方式還是以流的方式,您都可以通過 LobCreator 和 LobHandler 方便地訪問 LOB 數(shù)據(jù)。對于 ORM 框架來說,JPA 擁有自身處理 LOB 數(shù)據(jù)的配置類型,Spring 為 Hibernate 和 iBatis 分別提供了 LOB 數(shù)據(jù)類型的配置類,您僅需要使用這些類進行簡單的配置就可以像普通類型一樣操作 LOB 類型數(shù)據(jù)。
![]() ![]() |
![]()
|
當您在 Web 應用服務器或 Spring 中配置數(shù)據(jù)源時,從數(shù)據(jù)源中返回的數(shù)據(jù)連接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類,這是因為數(shù)據(jù)源需要改變數(shù)據(jù)連接一些原有的行為以便對其進行控制:如調(diào)用 Connection#close()
方法時,將數(shù)據(jù)連接返回到連接池中而非將其真的關閉。
在訪問 LOB 數(shù)據(jù)時,根據(jù)數(shù)據(jù)庫廠商的不同,可能需要使用被代理前的本地 JDBC 對象(如 DB2Connection 或 DB2ResultSet)特有的 API。為了從數(shù)據(jù)源中獲取本地 JDBC 對象, Spring 定義了 org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
接口并提供了相應的實現(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
對象進行代理,這時可以直接使用 SimpleNativeJdbcExtractor
實現(xiàn)類。但有些數(shù)據(jù)源(如 Jakarta Commons DBCP)會對所有的 JDBC 對象進行代理,這時,就需要根據(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; ③ 這時可以強制進行類型轉換了 … } catch (Exception e) { e.printStackTrace(); } } } |
在 ① 處我們通過 DataSourceUtils
獲取當前線程綁定的數(shù)據(jù)連接,為了使用線程上下文相關的事務,通過 DataSourceUtils
從數(shù)據(jù)源中獲取連接是正確的做法,如果直接通過 dateSource
獲取連接,則將得到一個和當前線程上下文無關的數(shù)據(jù)連接實例。
JdbcTemplate 可以在配置時注入一個本地 JDBC 對象抽取器,要使代碼 清單 1 正確運行,我們必須進行如下配置:
清單 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" /> ② 設置抽取器 <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 對象進行操作。
雖然 JDBC 定義了兩個操作 LOB 類型的接口:java.sql.Blob
和 java.sql.Clob
,但有些廠商的 JDBC 驅動程序并不支持這兩個接口。為此,Spring 定義了一個獨立于 java.sql.Blob/Clob
的 LobCreator
接口,以統(tǒng)一的方式操作各種數(shù)據(jù)庫的 LOB 類型數(shù)據(jù)。因為 LobCreator 本身持有 LOB 所對應的數(shù)據(jù)庫資源,所以它不是線程安全的,一個 LobCreator 只能操作一個 LOB 數(shù)據(jù)。
為了方便在 PreparedStatement 中使用 LobCreator,您可以直接使用 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc)
方法。下面對 LobCreator 接口中的方法進行簡要說明:
方法 | 說明 |
---|---|
void close() |
關閉會話,并釋放 LOB 資源 |
void setBlobAsBinaryStream(PreparedStatement ps, int paramIndex, InputStream contentStream, int contentLength) |
通過流填充 BLOB 數(shù)據(jù) |
void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) |
通過二進制數(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
接口為操作 BLOB/CLOB 提供了統(tǒng)一訪問接口,而不管底層數(shù)據(jù)庫究竟是以大對象的方式還是以一般數(shù)據(jù)類型的方式進行操作。此外,LobHandler 還充當了 LobCreator 的工廠類。
大部分數(shù)據(jù)庫廠商的 JDBC 驅動程序(如 DB2)都以 JDBC 標準的 API 操作 LOB 數(shù)據(jù),但 Oracle 9i 及以前的 JDBC 驅動程序采用了自己的 API 操作 LOB 數(shù)據(jù),Oracle 9i 直接使用自己的 API 操作 LOB 數(shù)據(jù),且不允許通過 PreparedStatement 的 setAsciiStream()
、setBinaryStream()
、setCharacterStream()
等方法填充流數(shù)據(jù)。Spring 提供 LobHandler
接口主要是為了遷就 Oracle 特立獨行的作風。所以 Oracle 必須使用 OracleLobHandler
實現(xiàn)類,而其它的數(shù)據(jù)庫統(tǒng)一使用 DefaultLobHandler
就可以了。Oracle 10g 改正了 Oracle 9i 這個異化的風格,終于天下歸一了,所以 Oracle 10g 也可以使用 DefaultLobHandler
。 下面,我們來看一下 LobHandler
接口的幾個重要方法:
方法 | 說明 |
---|---|
InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) |
從結果集中返回 InputStream,通過 InputStream 讀取 BLOB 數(shù)據(jù) |
byte[] getBlobAsBytes(ResultSet rs, int columnIndex) |
以二進制數(shù)據(jù)的方式獲取結果集中的 BLOB 數(shù)據(jù); |
InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) |
從結果集中返回 InputStream,通過 InputStreamn 以 Ascii 字符流方式讀取 BLOB 數(shù)據(jù) |
Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) |
從結果集中獲取 Unicode 字符流 Reader,并通過 Reader以Unicode 字符流方式讀取 CLOB 數(shù)據(jù) |
String getClobAsString(ResultSet rs, int columnIndex) |
從結果集中以字符串的方式獲取 CLOB 數(shù)據(jù) |
LobCreator getLobCreator() |
生成一個會話相關的 LobCreator 對象 |
![]() ![]() |
![]()
|
在 Spring JDBC 中操作 LOB 數(shù)據(jù)
假設我們有一個用于保存論壇帖子的 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()); ③ 設置 CLOB 字段 lobCreator.setClobAsString(ps, 3, post.getPostText()); ④ 設置 BLOB 字段 lobCreator.setBlobAsBytes(ps, 4, post.getPostAttach()); } }); } … } |
首先,我們在 PostJdbcDao
中引入了一個 LobHandler
屬性,如 ① 所示,并通過 JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc)
方法完成插入 LOB 數(shù)據(jù)的操作。我們通過匿名內(nèi)部類的方式定義 LobCreatingPreparedStatementCallback
抽象類的子類,其構造函數(shù)需要一個 LobHandler
入?yún)ⅲ?② 所示。在匿名類中實現(xiàn)了父類的抽象方法 setValues(PreparedStatement ps,LobCreator lobCreator)
,在該方法中通過 lobCreator
操作 LOB 對象,如 ③、④ 所示,我們分別通過字符串和二進制數(shù)組填充 BLOB 和 CLOB 的數(shù)據(jù)。您同樣可以使用流的方式填充 LOB 數(shù)據(jù),僅需要調(diào)用 lobCreator 相應的流填充方法即可。
我們需要調(diào)整 Spring 的配置文件以配合我們剛剛定義的 PostJdbcDao。假設底層數(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"/> ① 設置本地 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
需要通過運行期的反射機制獲取底層的 JDBC 對象,所以需要避免在 Spring 容器啟動時就實例化這兩個 Bean。
LobHandler 需要訪問本地 JDBC 對象,這一任務委托給 NativeJdbcExtractor
Bean 來完成,因此我們在 ① 處為 LobHandler 注入了一個 nativeJdbcExtractor
。最后,我們把 lobHandler
Bean 注入到需要進行 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 只是簡單地代理標準 JDBC 的 PreparedStatement 和 ResultSet 對象,由于并不需要訪問數(shù)據(jù)庫驅動本地的 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); ① 以二進制數(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)存資源,對應用程序整體性能產(chǎn)生巨大的沖擊。對于體積很大的 LOB 數(shù)據(jù),我們可以使用流的方式進行訪問,減少內(nèi)存的占用。JdbcTemplate 為此提供了一個 Object query(String sql, Object[] args, ResultSetExtractor rse)
方法,ResultSetExtractor
接口擁有一個處理流數(shù)據(jù)的抽象類 org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor
,可以通過擴展此類用流的方式操作 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); } } } ); } |
通過擴展 AbstractLobStreamingResultSetExtractor
抽象類,在 streamData(ResultSet rs)
方法中以流的方式讀取 LOB 字段數(shù)據(jù),如 ④ 所示。這里我們又利用到了 Spring 的工具類 FileCopyUtils
將輸入流的數(shù)據(jù)拷貝到輸出流中。在 getAttach()
方法中通過入?yún)?OutputStream os
接收 LOB 的數(shù)據(jù),如 ① 所示。您可以同時覆蓋抽象類中的 handleNoRowFound()
方法,定義未找到數(shù)據(jù)行時的處理邏輯。
![]() ![]() |
![]()
|
在 JPA 中 LOB 類型的持久化更加簡單,僅需要通過特殊的 LOB 注釋(Annotation)就可以達到目的。我們對 Post 中的 LOB 屬性類型進行注釋:
清單 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 不采用延遲加載機制 @Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL") ①-3 對應字段類型 private String postText; @Lob ②-1 表示該屬性是 LOB 類型的字段 @Basic(fetch = FetchType. LAZY) ②-2 采用延遲加載機制 @Column(name = "POST_ATTACH", columnDefinition = "BLOB") ②-3 對應字段類型 private byte[] postAttach; … } |
postText
屬性對應 T_POST
表的 POST_TEXT
字段,該字段的類型是 LONTTEXT
,并且非空。JPA 通過 @Lob
將屬性標注為 LOB 類型,如 ①-1 和 ②-1 所示。通過 @Basic
指定 LOB 類型數(shù)據(jù)的獲取策略,FetchType.EAGER
表示非延遲加載,而 FetchType.LAZY
表示延遲加載,如 ①-2 和 ②-2 所示。通過 @Column
的 columnDefinition
屬性指定數(shù)據(jù)表對應的 LOB 字段類型,如 ①-3 和 ②-3 所示。
關于 JPA 注釋的更多信息,請閱讀 參考資源 中的相關技術文章。
![]() ![]() |
![]()
|
在 Hibernate 中操作 LOB 數(shù)據(jù)
![]() |
|
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"/>①對應 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
類型的屬性,對應數(shù)據(jù)庫的 CLOB 類型,而 postAttach
為 byte[]
類型的屬性,對應數(shù)據(jù)庫的 BLOB 類型。分別使用 Spring 所提供的相應 UserType
實現(xià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" /> ① 設置 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ù)時,需要在事務環(huán)境下才能工作,所以必須事先配置事務管理器,否則會拋出異常。
![]() ![]() |
![]()
|
iBatis 為處理不同類型的數(shù)據(jù)定義了一個統(tǒng)一的接口:com.ibatis.sqlmap.engine.type.TypeHandler
。這個接口類似于 Hibernate 的 UserType。iBatis 本身擁有該接口的眾多實現(xiàn)類,如 LongTypeHandler、DateTypeHandler 等,但沒有為 LOB 類型提供對應的實現(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
類型;
當結果集中包括 LOB 數(shù)據(jù)時,需要在結果集映射配置項中指定對應的 Handler 類,下面我們采用 Spring 所提供的實現(xiàn)類對 Post 結果集的映射進行配置。
清單 12 . 對 LOB 數(shù)據(jù)進行映射
<?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> |
![]() |
|
當 iBatis 引擎從結果集中讀取或更改 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" /> ①設置LobHandler <property name="configLocation" value="classpath:com/baobaotao/dao/ibatis/sql-map-config.xml" /> </bean> |
處理 LOB 數(shù)據(jù)時,Spring 要求在事務環(huán)境下工作,所以還必須配置一個事務管理器。iBatis 的事務管理器和 Spring JDBC 事務管理器相同,此處不再贅述。