当我使用jdbc瘦驱动程序搜索Web以将BLOB插入Oracle数据库时,大多数网页建议采用三步法:
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();
}
有一些网页作者建议使用更简单的一步解决方案。此解决方案的先前示例:
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字段。
答案 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。
DBMS_LOB.CREATETEMPORATY - 在这种情况下返回的定位符指向临时表空间。针对此定位器的所有写入/读取将通过网络发送到DB服务器。 JVM堆中没有存储任何内容。
调用EMPTY_BLOB函数。 插入T1(名称,文件)值(“a.avi”,EMPTY_BLOB())返回文件? 在这种情况下,返回的lob定位器指向数据表空间。针对此定位器的所有写入/读取将通过网络发送到DB服务器。通过写入重做日志来“保护”所有写入。 JVM堆中没有存储任何内容。 JDBC标准(1.0,2.0)不支持返回子句,因此您可以在互联网上找到许多人们建议采用两个步骤的示例:“INSERT ...; SELECT ... FOR UPDATE;”
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();