【IT168 技术文档】
一、概述
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 对象进行操作。
虽然 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 对象 |