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
包中爲咱們提供了相應的幫助類,以便咱們輕鬆應對這頭攔路虎。html
Spring 大大下降了咱們處理 LOB 數據的難度。首先,Spring 提供了 NativeJdbcExtractor
接口,您能夠在不一樣環境裏選擇相應的實現類從數據源中獲取本地 JDBC 對象;其次,Spring 經過 LobCreator
接口取消了不一樣數據廠商操做 LOB 數據的差異,並提供了建立 LobCreator 的LobHandler
接口,您只要根據底層數據庫類型選擇合適的 LobHandler 進行配置便可。java
本文將詳細地講述經過 Spring JDBC 插入和訪問 LOB 數據的具體過程。不論是以塊的方式仍是以流的方式,您均可以經過 LobCreator 和 LobHandler 方便地訪問 LOB 數據。對於 ORM 框架來講,JPA 擁有自身處理 LOB 數據的配置類型,Spring 爲 Hibernate 和 iBatis 分別提供了 LOB 數據類型的配置類,您僅須要使用這些類進行簡單的配置就能夠像普通類型同樣操做 LOB 類型數據。web
回頁首spring
當您在 Web 應用服務器或 Spring 中配置數據源時,從數據源中返回的數據鏈接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類,這是由於數據源須要改變數據鏈接一些原有的行爲以便對其進行控制:如調用 Connection#close()
方法時,將數據鏈接返回到鏈接池中而非將其真的關閉。sql
在訪問 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 對象 |
apache
有些簡單的數據源僅對 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 的方法:安全
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 正確運行,咱們必須進行以下配置:
… <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 對象進行操做。
雖然 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
接口爲操做 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 對象 |
假設咱們有一個用於保存論壇帖子的 t_post
表,擁有兩個 LOB 字段,其中 post_text
是 CLOB 類型,而 post_attach
是 BLOB 類型。下面,咱們來編寫插入一個帖子記錄的代碼:
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,能夠採用如下的配置方式:
… <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 中,如 ② 所示。
若是底層數據庫是 DB二、SQL Server、MySQL 等非 Oracle 的其它數據庫,則只要簡單配置一個 DefaultLobHandler
就能夠了,以下所示:
<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()
方法:
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 數據:用 String
讀取 CLOB 字段的數據,用 byte[]
讀取 BLOB 字段的數據。在 PostJdbcDao 中添加一個 getAttachs()
方法,以便獲取某一用戶的全部帶附件的帖子:
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 數據可能很大(如 100M),若是直接以塊的方式操做 LOB 數據,須要消耗大量的內存資源,對應用程序總體性能產生巨大的衝擊。對於體積很大的 LOB 數據,咱們可使用流的方式進行訪問,減小內存的佔用。JdbcTemplate 爲此提供了一個 Object query(String sql, Object[] args, ResultSetExtractor rse)
方法,ResultSetExtractor
接口擁有一個處理流數據的抽象類org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor
,能夠經過擴展此類用流的方式操做 LOB 字段的數據。下面咱們爲 PostJdbcDao 添加一個以流的方式獲取某個帖子附件的方法:
… 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 類型的持久化更加簡單,僅須要經過特殊的 LOB 註釋(Annotation)就能夠達到目的。咱們對 Post 中的 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 註釋的更多信息,請閱讀 參考資源 中的相關技術文章。
使用 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 所示:
<?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 數據的能力:
… <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 爲處理不一樣類型的數據定義了一個統一的接口: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 結果集的映射進行配置。
<?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 頭疼的問題將變得輕鬆了許多。