第 七 章  存储二进制数据

    PostgreSQL 提供两种不同的方法来存储二进制数据。二进制数据可以被存储在一个使用bytea类型的表中,或者把大对象用特殊格式的二进制存储在一个分离的参照表中,而在你的表中存储该参照表的oid值。

    为了确定哪种方法比较适合,你需要理解每种方法的局限性。用bytea数据类型的方法不太适合于存储大量的二进制数据,虽然一个butea的属性可以存储高达1GB的二进制数据,但是它需要占用大量内存来完成这个大数据。大对象方法在存储非常大数据量的对象方面略胜一筹,但是它也有它的局限性。特别是在删除某行含有大对象参照的时候实际上并没有删除大对象,要删除大对象需要额外的动作去完成。大对象也有安全方面的问题,因为任何连接到数据库的人可以看到并/或者修改任何大对象,即使他们没有查看或更新含有大对象参照的行的权力。

    7.2版本第一次使JDBC驱动程序支持bytea的数据类型,在7.2的功能介绍中曾介绍了与之前版本的性能比较。从7.2开始,方法getBytes(),setBytes(),getBinaryStream()以及setBinaryStream()用来操作bytea数据类型。在7.1及其以前版本,这些方法用来操作与大对象关联的oid数据类型。通过设置相容的恢复驱动程序到7.1以前版本来连接7.1以前的对象是有可能的。详见"连接参数"一章。

    你可以仅使用getBytes(),setBytes(),getBinaryStream()以及setBinaryStream()方法来使用byte数据类型。

    你可以用使用PostgreSQL的JDBC驱动程序提供的大对象类型或者使用getBLOB()和setBLO B()方法。

 
Note
    你必须在SQL事物块当中操作大对象,你可以使用函数setAutoCommit(false)启动事物块。
 

    例7.1 "处理JDBC中的二进制数据"包含一些访问PostgreSQL的JDBC驱动程序的二进制数据的例子。

    例7.1 处理JDBC中的二进制数据

    例,假设你有一个表包含图象的文件名,同时你想存储图象在一个bytea的列中:
            CREATE TABLE images (imgname text, img bytea);

    下面代码实现插入一图象:

 
  File file = new File("myimage.gif");
  FileInputStream fis = new FileInputStream(file);
  PreparedStatement ps = conn.prepareStatement("INSERT INTO images VALUES (?, ?)");

  ps.setString(1, file.getName());
  ps.setBinaryStream(2, fis, file.length());
  ps.executeUpdate();
  ps.close();
  fis.close();

    这里,setBinaryStream()把一个字节流集合转换成bytea类型的列值。如果图象内容已经是byte[]类型,用setBytes()方法也可以实现。

 
Note
    setBinaryStream的参数length是合适的。没有方法显示流的长度是不可知的。如果是这种情况,你必须亲自读取流到一个临时存储器中并计算长度,然后你才可以从临时存储器中发送正确的长度到驱动程序上。
 

    先找到图象(我们这里采用PreparedStatement,但是Statement类也可以使用的)。

 
  PreparedStatement ps =conn.prepareStatement("SELECT img FROM images WHERE imgname = ?");

  ps.setString(1, "myimage.gif");
  ResultSet rs = ps.executeQuery();
  while (rs.next()) {
  byte[] imgBytes = rs.getBytes(1);
  // 用一种方法表示数据
  }
  rs.close();
  ps.close();

    这里二进制数据被恢复为byte[],你可以用InputStream对象代替。你也可以选择用大对象API来存储存储大文件。
        CREATE TABLE imageslo (imgname text, imgoid oid);

可以用以下方法插入图像:
 
// 所有大对象API调用都必须在事物块内
    conn.setAutoCommit(false);
// 取得Large Object Manager来操作
    LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();
  
// 创建一个新的大对象
    int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);
// 打开大对象来写
    LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);
// 打开文件
    File file = new File("myimage.gif");
    FileInputStream fis = new FileInputStream(file);
// 从文件中读取数据并复制到大对象中
    byte buf[] = new byte[2048];
    int s, tl = 0;
    while ((s = fis.read(buf, 0, 2048)) > 0) {
        obj.write(buf, 0, s); tl += s;
    }
// 关闭large object
    obj.close();
// Now insert the row into imageslo
    PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");

    ps.setString(1, file.getName());
    ps.setInt(2, oid);
    ps.executeUpdate();
    ps.close();
    fis.close();
// Finally, commit the transaction.
    conn.commit();

    从大对象中取回图片:
 
// All LargeObject API calls must be within a transaction block
     conn.setAutoCommit(false);
// Get the Large Object Manager to perform operations with
     LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();

    PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?");


    ps.setString(1, "myimage.gif");
    ResultSet rs = ps.executeQuery();
    while (rs.next()) {
// Open the large object for reading
        int oid = rs.getInt(1);
        LargeObject obj = lobj.open(oid, LargeObjectManager.READ);
// Read the data
        byte buf[] = new byte[obj.size()];
        obj.read(buf, 0, obj.size());
// Do something with the data read here
// Close the object
        obj.close();
    }
    rs.close();
    ps.close();
// Finally, commit the transaction.
    conn.commit();