过于复杂的 oracle jdbc BLOB handling

2022-09-01 09:55:30

当我使用jdbc精简驱动程序搜索将BLOB插入Oracle数据库时,大多数网页都建议采用3步方法:

  1. 插入值。empty_blob()
  2. 选择带有 的行。for update
  3. 插入实际值。

这对我来说很好,这里有一个例子:

Connection oracleConnection = ...

byte[] testArray = ...

PreparedStatement ps = oracleConnection.prepareStatement(
    "insert into test (id, blobfield) values(?, empty_blob())");
ps.setInt(1, 100);
ps.executeUpdate();
ps.close();
ps = oracleConnection.prepareStatement(
    "select blobfield from test where id = ? for update");
ps.setInt(1, 100);
OracleResultSet rs = (OracleResultSet) ps.executeQuery();
if (rs.next()) {
    BLOB blob = (BLOB) rs.getBLOB(1);
    OutputStream outputStream = blob.setBinaryStream(0L);
    InputStream inputStream = new ByteArrayInputStream(testArray);
    byte[] buffer = new byte[blob.getBufferSize()];
    int byteread = 0;
    while ((byteread = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, byteread);
    }
    outputStream.close();
    inputStream.close();
}

有些网页的作者建议使用更简单的1步解决方案。此解决方案的上一个示例:

Connection oracleConnection = ...

byte[] testArray = ...

PreparedStatement ps = oracleConnection.prepareStatement(
    "insert into test(id, blobfield) values(?, ?)");
BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);
OutputStream outputStream = blob.setBinaryStream(0L);
InputStream inputStream = new ByteArrayInputStream(testArray);
byte[] buffer = new byte[blob.getBufferSize()];
int byteread = 0;
while ((byteread = inputStream.read(buffer)) != -1) {
    outputStream.write(buffer, 0, byteread);
}
outputStream.close();
inputStream.close();

ps.setInt(1, 100);
ps.setBlob(2, blob);
ps.executeUpdate();
ps.close();

第二个代码要容易得多,所以我的问题是:第一个(流行的)解决方案的意义是什么?对于第二种解决方案,是否存在某种约束(Oracle 服务器版本号、jdbc 驱动程序版本、blob 的大小,...)?第一个解决方案是否更好(速度、内存消耗,...)?不使用更简单的第二种方法有什么理由?

完全相同的问题也适用于 CLOB 字段。


答案 1

您在第一种情况下提到的更新方法可以使用纯 JDBC 代码进行重写,从而减少对 Oracle 特定类的依赖性。如果你的应用需要与数据库无关,这可能很有用。

public static void updateBlobColumn(Connection con, String table, String blobColumn, byte[] inputBytes, String idColumn, Long id) throws SQLException {
  PreparedStatement pStmt = null;
  ResultSet rs = null;
  try {
    String sql = 
      " SELECT " + blobColumn + 
      " FROM " + table + 
      " WHERE " + idColumn + " = ? " +
      " FOR UPDATE";
    pStmt = con.prepareStatement(sql, 
      ResultSet.TYPE_FORWARD_ONLY, 
      ResultSet.CONCUR_UPDATABLE);
    pStmt.setLong(1, id);
    rs = pStmt.executeQuery();
    if (rs.next()) {
      Blob blob = rs.getBlob(blobColumn);
      blob.truncate(0);
      blob.setBytes(1, inputBytes);
      rs.updateBlob(blobColumn, blob);
      rs.updateRow();
    }
  }
  finally {
    if(rs != null) rs.close();
    if(pStmt != null) pStmt.close();
  }
}

对于MSSQL,我理解锁定语法是不同的:

String sql = 
  " SELECT " + blobColumn + 
  " FROM " + table + " WITH (rowlock, updlock) " + 
  " WHERE " + idColumn + " = ? "

答案 2

Oracle DBA的另一个观点。Sun在设计JDBC标准(1.0,2.0,3.0,4.0)时做得非常差。BLOB代表大型对象,因此它可以非常大。它是不能存储在 JVM 堆中的东西。Oracle认为BLOB类似于文件句柄(事实上,它们被称为“lob定位器”)。LOBS 不能通过构造函数创建,也不是 Java 对象。此外,LOB定位器(oracle.sql.BLOB)不能通过构造函数创建 - 它们必须在数据库端创建。在 Oracle 中,有两种方法可以创建 LOB。

  1. DBMS_LOB。创建临时 - 在本例中,返回的定位器指向临时表空间。针对此定位器的所有写入/读取都将通过网络发送到 DB 服务器。JVM 堆中不存储任何内容。

  2. 调用EMPTY_BLOB函数。插入到 T1(名称,文件) 值 (“a.avi”, EMPTY_BLOB()) 返回文件到 ?;在本例中,将 lob 定位器点返回到数据表空间中。针对此定位器的所有写入/读取都将通过网络发送到 DB 服务器。所有写入都通过写入重做日志来“保护”。JVM 堆中不存储任何内容。JDBC标准(1.0,2.0)不支持返回子句,因此您可以在互联网上找到许多示例,其中人们建议采用两个步骤的方法:“INSERT...;选择。。。更新;”

Oracle lobs必须与某些数据库连接相关联,当数据库连接丢失/关闭/(或“提交”)时,不能使用它们。它们不能从一个连接传递到另一个连接。

第二个示例可以工作,但如果数据从临时表空间复制到数据表空间,则需要过多复制。


推荐