2009-05-14 28 views
30

Cuando busco la web para insertar BLOB en la base de datos de Oracle con el controlador fino JDBC, la mayoría de las páginas web sugieren un enfoque de 3 pasos:overcomplicated JDBC de Oracle BLOB

  1. inserción empty_blob() valor.
  2. seleccione la fila con for update.
  3. inserte el valor real.

Esto funciona muy bien para mí, aquí es un ejemplo:

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

Hay algunas páginas web donde los autores sugieren el uso de una solución más simple de 1 paso. ejemplo anterior con esta solución:

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

El segundo código es mucho más fácil, por lo que mi pregunta es: ¿Cuál es el punto de la primera solución (popular)? ¿Existe (hubo) algún tipo de restricción para la segunda solución (número de versión del servidor Oracle, versión del controlador jdbc, tamaño del blob, ...)? ¿Es la primera solución mejor (velocidad, consumo de memoria, ...)? ¿Alguna razón para no usar el segundo enfoque más simple?

La misma pregunta es válida para los campos CLOB.

Respuesta

5

El manejo de LOB del servidor Oracle es bastante pobre y puede sufrir graves problemas de rendimiento (por ejemplo, sobreuso masivo del registro de rehacer), por lo que la primera solución puede ser una forma de abordarlos.

Sugeriría probar ambos enfoques. si tiene un DBA competente, es posible que pueda aconsejar qué enfoque tiene el menor impacto en el servidor.

11

El enfoque de actualización que menciona en el primer caso se puede reescribir utilizando el código JDBC puro y así reducir su dependencia en las clases específicas de Oracle. Esto podría ser útil si su aplicación necesita ser independiente de la base de datos.

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

Para MSSQL entiendo que la sintaxis de bloqueo es diferente:

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

¿La base de datos de la cláusula "FOR UPDATE" es independiente? No parece funcionar en SQL Server, por ejemplo. Pero, de todos modos, no es esencial para la técnica anterior ... –

+0

La cláusula FOR UPDATE indica a la base de datos que bloquee las filas. Es importante en este caso porque el conjunto de resultados actualizará esas filas. Si está razonablemente seguro de que nadie más actualizará esas filas, es posible que no necesite el bloqueo. –

+5

No está insertando un BLOB aquí, solo lo está actualizando. El OP fue específicamente sobre la inserción. –

5

Una cosa interesante con JDBC es que se puede mejorar más agresivamente a los controladores más recientes y trabajar con JDBC 4.0 características. Los controladores Oracle JDBC funcionarán con versiones de bases de datos más antiguas, por lo que puede usar un controlador JDBC de marca 11g en una base de datos 10g. La base de datos Oracle 11g JDBC viene en dos formas: ojdbc5.jar para Java 5 (es decir, JDK 1.5) y ojdbc6.jar para Java 6 (es decir, JDK 1.6). El archivo ojdbc6.jar admite la nueva especificación JDBC 4.0.

Con los nuevos controladores/JDBC 4.0 puede crear BLOB y CLOB fuera el objeto de conexión:

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

Desafortunadamente Oracle hizo un mal trabajo implementando la forma estándar JDBC de manejo BLOB (tal vez por razones comerciales). Te obligan a trabajar con clases concretas de Oracle para hacer el trabajo correctamente. – akarnokd

+0

Parece que no funciona para mí: contenedor JEE y Oracle 10.2. –

+0

@LuisSoeiro - Verifique qué controlador JDBC está usando. –

4

Esta afirmación:

blob.setBytes(1, inputBytes); 

está dando problemas cuando uso oráculo ojdbc14 cliente ligero .tarro, "Funciones no compatibles"

Por lo tanto, tenía que evitar por:

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

proporcionó los datos CLOB es lo suficientemente pequeño como para caber en su memoria sin volar, sólo puede crear una declaración preparada y simplemente llaman

ps.setString(1, yourString); 

puede haber otras limitaciones de tamaño, pero parece que funciona para los tamaños que estamos tratando (500 KB como máximo).

7

Otro punto de vista de Oracle DBA. Los chicos de Sun hicieron muy mal trabajo cuando diseñaron los estándares JDBC (1.0, 2.0, 3.0, 4.0). BLOB significa objeto grande y, por lo tanto, puede ser muy grande. Es algo que no se puede almacenar en el montón de JVM. Oracle piensa que los BLOB son algo así como manejadores de archivos (de hecho, se llaman entonces "localizadores de lob"). Los LOBS no se pueden crear a través del constructor y no son objetos de Java. Además, los localizadores LOB (oracle.sql.BLOB) no se pueden crear a través del constructor, DEBEN crearse en el lado DB. En Oracle hay dos maneras de cómo crear un LOB.

  1. DBMS_LOB.CREATETEMPORATY - el localizador devuelto en este caso apunta al espacio de tabla temporal. Todas las escrituras/lecturas contra este localizador se enviarán a través de la red al servidor de BD. No se almacena nada en el montón de JVM.

  2. Llamada a la función EMPTY_BLOB. INSERTAR EN T1 (NOMBRE, ARCHIVO) VALORES ("a.avi", EMPTY_BLOB()) ¿ENVIAR ARCHIVO A? En este caso, los puntos del localizador de lob vuelven al espacio de tabla de datos. Todas las escrituras/lecturas contra este localizador se enviarán a través de la red al servidor de BD. Todas las escrituras son "guardadas" por escrituras en redo-logs. No se almacena nada en el montón de JVM. La cláusula de devolución no era compatible con los estándares JDBC (1.0, 2.0), por lo tanto, puede encontrar muchos ejemplos en Internet donde las personas recomiendan el enfoque de dos pasos: "INSERTAR ...; SELECCIONAR ... PARA ACTUALIZAR";

Oracle lanza debe estar asociado con algún tipo de conexión de base de datos, no se puede utilizar cuando conexión DB se pierde/cerrado/(o "comprometida"). No se pueden pasar de una conexión a otra.

El segundo ejemplo puede funcionar, pero requerirá una copia excesiva si los datos del espacio de tabla temporal en el espacio de tabla de datos.

+0

Entonces, en su último caso, ¿cómo se escribiría el código java? ¿Cómo debería "DEVOLVER EL ARCHIVO A"? ¿ser manejado? –

+0

No entiendo esta respuesta completamente en términos de lo que realmente permite JDBC 4.0, como tener una declaración preparada como: stmt = conn.prepareStatement ("insertar en almacenamiento (storage.storageid, storage.attachment) values ​​(?, ?) "); ... stmt.setBlob (1, fInputStream); ... ¿Qué quieres decir exactamente cuando dices en Oracle que solo existen esas dos formas de crear Blobs, ya que la sentencia anterior funciona en Oracle (con el controlador ojdbc6.jar)? – ammianus

+2

OMG - Solo quiero insertar algunas líneas de texto de una cadena en un "BLOB" en un esquema que heredé. The String no es MB, ni mucho menos GB, de datos, PERO, a veces supera el límite asombroso de 2000 caracteres. Que circo – Roboprog

1

Encontré una simple llamada al setObject(pos, byte[]) funciona para mi caso. De Programación Base de datos con JDBC y Java por 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(); 
+1

Probablemente sea una buena idea forzar el tipo agregando [java.sql.Types.BLOB] (http://docs.oracle.com/javase/7/docs/api/java/sql/Types.html#BLOB) a la lista de argumentos de 'setObject'. – ceving

+0

Gracias, @ceving. Aprendí algo :) Actualicé la respuesta – Kirby

2

Algunos watchouts encontrados para la segunda solución

estoy usando ojdbc6.jar - la última versión y de la declaración de 'la segunda solución ':

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

tengo que liberar burbuja después de completar la declaración - o de otra burbuja se cierra cuando se cierra la sesión (que puede tomar mucho tiempo con la agrupación de conexiones).

blob.freeTemporary(); 

recursos De lo contrario se puede ver bloqueado:

select * from v$temporary_lobs 

Otro problema con gotas temporal es la necesidad de asignar espacio de tabla temporal: según la documentación http://docs.oracle.com/cd/E11882_01/appdev.112/e18294.pdf

Gestión de tablas temporal para los LOB temporales Espacio de tabla temporal se utiliza para almacenar datos de LOB temporales

0

Si tamaño de BLOB inserción es mayor que blob.getBufferSize(), la transacción se compromete, tan pronto como el primer trozo se escribe en dB valor predeterminado de la propiedad autoCommit de conexión JDBC es verdaderos y más trozos escrituras fallan como db los trata como nuevas transacciones. Se sugiere lo siguiente:
a) Establezca la propiedad jdbc connection autoCommit en false.

conn.setAutoCommit(false); 

b) explícitamente confirmar la transacción después de cargar todo el BLOB.

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