32

当我在网上搜索使用 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 字段。

4

9 回答 9

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 + " = ? "
于 2009-05-14T13:15:18.660 回答
7

Another point of view from Oracle DBA. Sun guys did very poor job when they designed JDBC standards(1.0, 2.0, 3.0, 4.0). BLOB stands for large object and therefore it can be very large. It is something that can not be stored in JVM heap. Oracle thinks of BLOBs as something like file handles(it fact they are call then "lob locators"). LOBS can not be created via constructor and are not Java objects. Also LOB locators(oracle.sql.BLOB) can not be created via constructor - they MUST be created in the DB side. In Oracle there are two ways how to create a LOB.

  1. DBMS_LOB.CREATETEMPORATY - the returned locator in this case points into temporary tablespace. All the writes/reads against this locator will be sent via network onto DB server. Nothing is stored in JVM heap.

  2. Call to EMPTY_BLOB function. INSERT INTO T1(NAME, FILE) VALUES("a.avi", EMPTY_BLOB()) RETURNING FILE INTO ?; In this case returned lob locator points into data tablespace. All the writes/reads against this locator will be sent via network onto DB server. All the writes are "guarded" by writes into redo-logs. Nothing is stored in JVM heap. The returning clause was not supported by JDBC standards (1.0, 2.0), therefore you can find many examples on the internet where people recommend approach of two steps: "INSERT...; SELECT ... FOR UPDATE;"

Oracle lobs must be associated with some database connection, they can not be used when DB connection is lost/closed/(or "commited"). They can not be passed from one connection to another.

You second example can work, but will require excessive copying if data from temporary tablespace into data tablespace.

于 2012-03-15T15:20:04.273 回答
5

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);
于 2009-05-17T03:17:17.980 回答
5

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

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

于 2009-05-14T09:59:11.113 回答
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();
于 2010-08-17T21:36:36.037 回答
2

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

ps.setString(1, yourString);

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

于 2011-07-04T22:10:10.627 回答
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 数据

于 2012-10-01T13:29:54.633 回答
1

setObject(pos, byte[])我找到了一个适合我的案例的简单调用。来自George Reese的 JDBC 和 Java 数据库编程,

        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();
于 2012-06-25T17:12:53.160 回答
0

如果插入 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();
于 2016-08-20T06:08:08.247 回答