您的位置:首页 > 编程语言 > Java开发

在 Spring JDBC 中操作 LOB 数据

2010-04-29 10:10 441 查看
作者: niyk 来源:赛迪网技术社区 http://bbs.tech.ccidnet.com/read.php?tid=527682
插入 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()); } }); }…}[/pre]

首先,我们在 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 配置
… ① 设置本地 Jdbc 对象抽取器 ② 设置 LOB 处理器[/pre]

大家可能已经注意到 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 的配置
[/pre]

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); }}[/pre]

这里,有几个知识点需要稍微解释一下: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; } });}[/pre]

通过 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); } } } );}[/pre]

通过扩展 AbstractLobStreamingResultSetExtractor 抽象类,在 streamData(ResultSet rs) 方法中以流的方式读取 LOB 字段数据,如 ④ 所示。这里我们又利用到了 Spring 的工具类 FileCopyUtils 将输入流的数据拷贝到输出流中。在 getAttach() 方法中通过入参 OutputStream os 接收 LOB 的数据,如 ① 所示。您可以同时覆盖抽象类中的 handleNoRowFound() 方法,定义未找到数据行时的处理逻辑。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: