过于复杂的 oracle jdbc BLOB 处理

Posted

技术标签:

【中文标题】过于复杂的 oracle jdbc BLOB 处理【英文标题】:Overcomplicated oracle jdbc BLOB handling 【发布时间】:2010-10-26 03:06:58 【问题描述】:

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

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

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

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 + " = ? "

【讨论】:

“FOR UPDATE”子句是否与数据库无关?例如,它似乎不适用于 SQL Server。但也许它对上述技术并不是必不可少的...... FOR UPDATE 子句指示数据库锁定行。在这种情况下这很重要,因为结果集将更新这些行。如果您有理由确定没有其他人会更新这些行,则您可能不需要锁。 您没有在此处插入 BLOB,您只是在更新它。 OP 专门用于插入。【参考方案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。

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

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

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

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

【讨论】:

那么在您的最后一种情况下,Java 代码将如何编写?应该如何“将文件返回到”?处理? 就 JDBC 4.0 实际允许的内容而言,我不完全理解这个答案,例如有一个准备好的语句,例如: stmt = conn.prepareStatement("insert into storage (storage.storageid,storage .attachment) 值 (?, ?)"); ... stmt.setBlob(1, fInputStream); ...当您在 Oracle 中说只有这两种创建 Blob 的方法时,您到底是什么意思,因为上述语句适用于 Oracle(使用 ojdbc6.jar 驱动程序)? OMG - 我只想将字符串中的几行文本插入到我继承的架构中的“BLOB”中。字符串不是 MB 的数据,更不用说 GB 的数据了,但是,它有时超过 2000 个字符的惊人限制。什么马戏团。【参考方案3】:

Oracle 服务器的 LOB 处理能力很差,可能会出现严重的性能问题(例如大量过度使用重做日志),因此第一个解决方案可能是解决这些问题的方法。

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

【讨论】:

【参考方案4】:

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

使用较新的驱动程序/jdbc 4.0,您可以从连接对象创建 Blob 和 Clob:

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

【讨论】:

不幸的是,Oracle 在实现 BLOB 处理的标准 JDBC 方式方面做得很差(可能是出于业务原因)。您必须使用具体的 Oracle 类才能正确完成工作。 它似乎对我不起作用:JEE 容器和 Oracle 10.2。 @LuisSoeiro - 验证您使用的 JDBC 驱动程序。【参考方案5】:

此声明:

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

【讨论】:

【参考方案6】:

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

ps.setString(1, yourString);

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

【讨论】:

【参考方案7】:

为第二种解决方案发现了一些注意事项

我正在使用 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 数据

【讨论】:

【参考方案8】:

我发现对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();

【讨论】:

通过将java.sql.Types.BLOB 添加到setObject 的参数列表来强制类型可能是个好主意。 谢谢你,@ceving。我学到了一些东西。 :) 我更新了答案【参考方案9】:

如果插入BLOB的大小大于blob.getBufferSize(),则事务在第一个块写入db时立即提交,因为jdbc连接的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();

【讨论】:

以上是关于过于复杂的 oracle jdbc BLOB 处理的主要内容,如果未能解决你的问题,请参考以下文章

Java -- JDBC 学习--处理Blob

jdbc oracle clob blob long类型数据

JDBC 复习3 存取Oracle大数据 clob blob

mybatis插入和修改oracle的Blob字段方法

如何在java中读取oracle blob

jdbc java数据库连接 11)中大文本类型的处理