这里,有几个知识点需要稍微解释一下: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 所示。
在 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 头疼的问题将变得轻松了许多。