过于复杂的oracle jdbc BLOB处理

时间:2009-05-14 09:15:35

标签: java oracle jdbc blob clob

当我使用jdbc瘦驱动程序搜索Web以将BLOB插入Oracle数据库时,大多数网页建议采用三步法:

  1. 插入empty_blob()值。
  2. 选择for update
  3. 插入真实值。
  4. 这对我来说很好,这是一个例子:

    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();
    }
    

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

    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字段。

9 个答案:

答案 0 :(得分:11)

您在第一种情况下提到的更新方法可以使用纯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 + " = ? "

答案 1 :(得分:7)

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

  1. DBMS_LOB.CREATETEMPORATY - 在这种情况下返回的定位符指向临时表空间。针对此定位器的所有写入/读取将通过网络发送到DB服务器。 JVM堆中没有存储任何内容。

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

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

    您的第二个示例可以正常工作,但如果数据从临时表空间进入数据表空间,则需要进行过多的复制。

答案 2 :(得分:5)

Oracle服务器的LOB处理非常差,并且可能会遇到严重的性能问题(例如,大量过度使用重做日志),因此第一种解决方案可能是解决这些问题的方法。

我建议尝试这两种方法。如果你有一个称职的DBA,他们可能会建议哪种方法对服务器的影响最小。

答案 3 :(得分:5)

JDBC的一个有趣之处在于,您可以相当积极地升级到最新的驱动程序并使用JDBC 4.0功能。 oracle JDBC驱动程序将与较旧的数据库版本一起使用,因此您可以对10g数据库使用11g品牌JDBC驱动程序。 Oracle数据库11g JDBC有两种形式:Java 5的ojdbc5.jar(即JDK 1.5)和Java 6的ojdbc6.jar(即JDK 1.6)。 ojdbc6.jar支持新的JDBC 4.0规范。

使用较新的drivers / jdbc 4.0,您可以从连接对象中创建Blob和Clobs:

Blob aBlob = con.createBlob();
int numWritten = aBlob.setBytes(1, val);

答案 4 :(得分:4)

本声明:

blob.setBytes(1, inputBytes);
使用oracle瘦客户机ojdbc14.jar时,

会出现问题,“不支持的功能”

所以,我不得不解决:

rset.next();
Blob bobj = rset.getBlob(1);
BLOB object = (BLOB) bobj;
int chunkSize = object.getChunkSize();
byte[] binaryBuffer = new byte[chunkSize];
int position = 1;
int bytesRead = 0;
int bytesWritten = 0, totbytesRead = 0, totbytesWritten = 0;
InputStream is = fileItem.getInputStream();
while ((bytesRead = is.read(binaryBuffer)) != -1) {
bytesWritten = object.putBytes(position, binaryBuffer, bytesRead);
position += bytesRead;
totbytesRead += bytesRead;
totbytesWritten += bytesWritten;
is.close();

答案 5 :(得分:2)

如果CLOB数据足够小以适应您的内存而不会爆炸,您只需创建一个准备好的语句并简单地调用

ps.setString(1, yourString);

可能存在其他尺寸限制,但它似乎适用于我们正在处理的尺寸(最大500kB)。

答案 6 :(得分:2)

为第二个解决方案找到了一些警告

我正在使用ojdbc6.jar - 最新版本以及“第二个解决方案”中的声明:

BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);

我必须在语句完成后释放blob - 或者在会话关闭时关闭blob(这可能需要很长时间才能建立连接池)。

blob.freeTemporary();

否则您可以看到锁定的资源:

select * from v$temporary_lobs

临时BLOB的另一个问题是需要分配临时表空间: 根据文件http://docs.oracle.com/cd/E11882_01/appdev.112/e18294.pdf

管理临时LOB的临时表空间 临时表空间用于存储临时LOB数据

答案 7 :(得分:1)

我发现对setObject(pos, byte[])的简单调用适合我的情况。 来自使用JDBC和Java进行数据库编程作者:George Reese,

        byte[] data = null;
        stmt = con.prepareStatement("INSERT INTO BlobTest(fileName, "
            + "blobData) VALUES(?, ?)");
        stmt.setString(1, "some-file.txt");
        stmt.setObject(2, data, Types.BLOB);
        stmt.executeUpdate();

答案 8 :(得分:0)

如果插入BLOB的大小大于blob.getBufferSize(),则只要第一个块写入db,就会提交事务,因为 jdbc connection的autoCommit属性的默认值为true ,并且进一步的块写入失败,因为db将它们视为新事务。建议如下:
a)将jdbc连接autoCommit属性设置为false。

conn.setAutoCommit(false);

b)上传整个BLOB后明确提交交易。

while ((bytesRead = messageInputStream.read(buffer)) != -1) {
     cumBytes += bytesRead;
     blobOutputStream.write(buffer, 0, bytesRead);
    }
conn.commit();