简介
诸如图像和音频这样的大型数据对象经常需要存储在数据库中。DB2 Universal Database 为存储大型数据对象提供了专门的数据类型。这些数据类型被称为大型对象(LOB)。在本文中,我们将在 LOB 的世界里遨游。更具体地说,我们将从 Java 程序开发者的角度来研究 LOB。您将看到如何在 Java 应用程序中存储以及检索 LOB。
业务场景
将您学到的东西应用到一个真实世界的例子总是有益的。因此,在本文中,我们将模拟一家书店。我们希望能够为网站访问者提供一种检索书的封面图像的方法。之后,您将看到我们怎样将书的封面图像存储为 BLOB。此外,我们还希望将书的摘要以 CLOB 数据类型存储在数据库中以便通过关键字来搜索。下面的图将使您了解在本文中我们将要完成的工作流。
数据类型的 LOB 家族
DB2 提供了三种不同的大型对象数据类型。所有这些数据类型都能存储最多达 2GB 的数据:
这些数据类型均属于 SQL3 数据类型。正如您将在本文后面看到的,JDBC 2.0 支持这些数据类型。
除非不得已,否则不要移动它
在存储器中移动大型对象是很占用资源的操作。因此,DB2 提供了与 LOB 交互的功能,该功能可以使这种移动减到最少。
数据在哪里?
注意到在 DB2 中 LOB 有一个重要且有趣的特点就是,LOB 的值没有存储在数据库的表中。实际上存储的是描述符,该描述符指向 LOB 的实际位置。真正的 LOB 值是存储在表空间里的,而表空间就是一些物理存储单元。
表示 LOB 值的定位器
实际上存储在 LOB 列中的不是真正的 LOB 数据,而是一个指向 LOB 数据的指针。该指针被称为“定位器”。这些所谓的定位器用于表示 LOB 值。当您在一个 ResultSet 中检索数据时,您将检索的是定位器而非这些定位器所表示的实际的 LOB 值。正如您将看到的,您必须显式地指出要检索的 LOB 值。用数据库术语来说,这种检索称作“具体化(materialization)”。
开始
让我们启动 DB2 Command Line Processor 来做 LOB 的实验。
首先,我们创建名为 "LOBDB" 的数据库:
db2 => create database LOBDB
使用用户名 db2admin 和密码 db2admin 来连接该数据库:
db2 => connect to LOBDB user db2admin using db2admin
出于学习的目的,我们将创建一对表,分别存储一个 BLOB 和一个 CLOB:
db2 => create table bookcovers (bookisbn varchar(10) not null, bookcover blob (100K) not null, primary key(bookisbn))
db2 => create table abstracts (bookisbn varchar(10) not null, bookabstract clob (100K) not null, primary key(bookisbn))
上面语句中的 100K 是声明我们需要存储的 LOB 的最大长度。此长度可以在 1B 到 2GB 中变化。当 LOB 被具体化时,应用程序将通过这个最大长度信息分配一个大小合适的缓冲区来容纳数据。
您可以使用下面的后缀来表示长度的字节数目:
K: 千字节 (1,024 个字节)
M: 兆字节 (1,048,576 个字节)
G: 千兆字节 (1,073,741,824 个字节)
在我们的两个表中,书是通过主键 bookishn 来区分的。bookisbn 这列就是表示书的 ISBN 编号。
是否压缩?
创建一个表时,您可以指定 COMPACT 或 NOT COMPACT 选项。如果指定了 COMPACT 选项,您所存储的 LOB 数据所占空间将最小。然而,如果您向 LOB 列执行更新操作且该操作会增加该 LOB 的大小,则会有性能损失。另一方面,如果您指定 NOT COMPACT 选项(默认值),实质上您的 LOB 值就可以有增长的余地。
是否作日志记录?
如果您指定了 LOGGED 选项,那么 LOB 列上的更新都将被记录在系统日志中。指定 LOGGED 选项可以为存储的数据提供最大保护。这样就可以通过向前恢复过程来重建数据以防发生介质故障。然而这是以牺牲磁盘空间为代价的(更不用说写到磁盘所付出的时间代价了)。如果您没有指定一个选项,则默认选中 LOGGED 选项。
插入 LOB
通过 Java 插入 BLOB 十分简单。请看下面的代码样本:
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO BOOKCOVERS VALUES(?,?)"); File imageFile = new File("c:\\\\redbookcover.jpg"); InputStream inputStream = new FileInputStream(imageFile); preparedStatement.setString(1," 0738425826"); preparedStatement.setBinaryStream(2,inputStream,(int)(imageFile.length())); preparedStatement.executeUpdate();
上面这个简短的代码片断从 C 盘根目录下取出名为 redbookcover.jpg
的文件并将其存储到数据库中。请注意我们是怎样将文件与一个 InputStream 相关联的。 此 InputStream 对象被用来填充针对 BLOB 列而存在的准备好的语句的值。此代码和本文中的其他代码都能够在本文对应的项目 zip 文件中找到。
CLOB 的插入与以上所示 BLOB 的插入几乎一模一样。您可以参阅名为 ClobInsertion.java
的项目文件。您将在此文件中看到我们是以 CLOB 的形式来存储书的摘要的。而摘要是从名为 redbookabstract.txt
的文本文件中抓取的。
检索 LOB
我们已经插入了一些 LOB。现在我们怎样在 Java 中检索它们呢?这也是个很简单的过程。
PreparedStatement preparedStatement = connection.prepareStatement("SELECT BOOKCOVER FROM BOOKCOVERS WHERE BOOKISBN=?"); preparedStatement.setString(1, "0738425826"); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { // materialization of the Blob Blob blob = resultSet.getBlob(1); InputStream inputStream = blob.getBinaryStream(); File fileOutput = new File("C:\\\\clonedredbookcover.jpg"); FileOutputStream fo = new FileOutputStream(fileOutput); int c; while ((c = inputStream.read()) != -1) fo.write(c); fo.close(); System.out.println("Blob retrieved");
上面的代码段中,我们执行了一个准备好的语句,我们在该语句中选择了在前面代码段中插入的那个 BLOB。需要注意的一个要点就是直到下面这行代码之后,真正的 BLOB 才被具体化:
InputStream inputStream = blob.getBinaryStream();
我们使用输入流来将检索到的 BLOB 存储在一个名为 clonedredbookcover.jpg
的文件中。
检索 CLOB 的语法大多跟检索 BLOB 很相似。请参阅项目文件 ClobRetrieval.java
。
JDBC API 提供了手段使我们可以对 CLOB 做一些有趣的操作。
请查看以下代码:
Clob clob = resultSet.getClob(1); //retrieve the first 200 characters String subString = clob.getSubString(1,200); System.out.println("First 200 characters of Clob (Book Abstract): " + subString); // find the position of a given String in the Clob long positionOfString = clob.position("tool",1); System.out.println("Position of SubString: " + positionOfString);
在上面的代码中,我们使用 getSubString
方法来获取存储的 CLOB 的一部分。这样的函数可以在仅仅需要书的摘要的部分内容的客户应用程序中使用。上面的代码还演示了 position
方法。如果一个给定的字符串包含在我们的 CLOB 中,该方法将返回一个 long 表示这个被搜索串在 CLOB 中的起始位置。如果该字符串没有包含在 CLOB 中,将返回 -1。CLOB 接口提供的 position 方法不需要将存储的数据具体化就可以运行。
CLOB 的实际具体化以如下代码方式出现:
File fileOutput = new File("C:\\\\clonedredbookabstract.txt"); FileOutputStream fo = new FileOutputStream(fileOutput); InputStream is = clob.getAsciiStream(); int c; while ((c = is.read()) != -1) fo.write(c); fo.close();
在上面的代码片断中,我们将存储的 CLOB 具体化成一个名为 clonedredbookabstract.txt
的新文件。我们使用 CLOB 中的 getAsciiStream
方法来完成该操作。正如在检索 BLOB 时所做的一样,我们先将数据流分配给 InputStream
,然后从中读取并将所读取的内容写入 FileOutputStream
。
结束语
在本文中,您看到了 DB2 UDB 是如何提供工具来存储大型数据对象的。使用 JDBC 2.0 API,您就可以通过 Java 与 LOB 交互。本文向您介绍了 JDBC API 的用法。我们是通过向您介绍一个真实世界的例子——一家书店需要存储封面图像和书的摘要来介绍其用法的。您现在应该可以在您自己的 Java 应用程序中熟练使用 LOB 了。
进阶读物
Developing Java applications using DB2 Image and Audio Extenders, Part 1
http://www-106.ibm.com/developerworks/library/it/it-1001art29/
DB2 Universal Database V8 Application Development - Java Application Development
http://www-3.ibm.com/software/data/db2/udb/ad/v8/java/
使用 DB2 版本 8 开发企业 Java 应用程序
http://www.ibm.com/developerWorks/cn/dmdd/library/techarticles/0209hutchison/index.shtml
Advanced Programming for the Java Platform - JDBC Technology
http://developer.java.sun.com/developer/onlineTraining/Programming/JDCBook/jdbc.html
致谢
感谢 Robert Indrigo 帮助我审核了本文。