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