2016-06-17 13 views
0

Ich versuche, in von BLOB s übergeben, aber ich bekomme Fehler.Spring JDBC - Übergabe von ARRAY von BLOBs an SQL-Funktion

uploadFiles = new SimpleJdbcCall(dataSource).withCatalogName("FILES_PKG") 
        .withFunctionName("insertFiles").withReturnValue() 
        .declareParameters(new SqlParameter("p_userId", Types.NUMERIC), 
          new SqlParameter("p_data", Types.ARRAY, "BLOB_ARRAY"), 
          new SqlOutParameter("v_groupId", Types.NUMERIC)); 
uploadFiles.compile(); 
List<Blob> fileBlobs = new ArrayList<>(); 

     for(int x = 0; x < byteFiles.size(); x++){ 
      fileBlobs.add(new javax.sql.rowset.serial.SerialBlob(byteFiles.get(x))); 
     } 

     final Blob[] data = fileBlobs.toArray(new Blob[fileBlobs.size()]); 

     SqlParameterSource in = new MapSqlParameterSource() 
       .addValue("p_files", new SqlArrayValue<Blob>(data, "BLOB_ARRAY")) 
       .addValue("p_userId", userId); 

     Map<String, Object> results = uploadFiles.execute(in); 

Ich habe eine SQL-Typ in der DB

create or replace TYPE BLOB_ARRAY is table of BLOB; 

Funktion Spec

FUNCTION insertFiles(p_userId IN NUMBER, 
       p_files IN BLOB_ARRAY) 
     RETURN NUMBER; 

Funktion Körper

FUNCTION insertFiles (p_userId IN NUMBER, 
         p_files IN BLOB_ARRAY) 
     RETURN NUMBER 
    AS 

     v_groupId NUMBER := FILE_GROUP_ID_SEQ.NEXTVAL; 
     v_fileId NUMBER; 
    BEGIN 

     FOR i IN 1..p_files.COUNT 
     LOOP 

     v_fileId := FILE_ID_SEQ.NEXTVAL; 
     BEGIN 
     INSERT INTO FILES 
     (FILE_ID, 
     FILE_GROUP_ID, 
     FILE_DATA, 
     UPDT_USER_ID) 
     SELECT 
     v_fileId, 
     v_groupId, 
     p_files(i), 
     USER_ID 
     FROM USERS 
     WHERE USER_ID = p_userId; 
     EXCEPTION WHEN OTHERS THEN 
     v_groupId := -1; 
     END; 

     END LOOP; 

     RETURN v_groupId; 
    END insertFiles; 

Ich bin nicht sicher, wie man richtig das Array übergeben von Blobs zur SQL-Funktion.

Fehler:

java.sql.SQLException: Fail to convert to internal representation: [email protected] at oracle.jdbc.oracore.OracleTypeBLOB.toDatum(OracleTypeBLOB.java:69) ~[ojdbc7.jar:12.1.0.1.0] at oracle.jdbc.oracore.OracleType.toDatumArray(OracleType.java:176) ~[ojdbc7.jar:12.1.0.1.0] at oracle.sql.ArrayDescriptor.toOracleArray(ArrayDescriptor.java:1321) ~[ojdbc7.jar:12.1.0.1.0] at oracle.sql.ARRAY.(ARRAY.java:140) ~[ojdbc7.jar:12.1.0.1.0] at

UPDATE

Nach Luke Vorschlag versucht, ich die folgende Störung erhalte:

uncategorized SQLException for SQL [{? = call FILES_PKG.INSERTFILES(?, ?)}]; SQL state [99999]; error code [22922]; ORA-22922: nonexistent LOB value ; nested exception is java.sql.SQLException: ORA-22922: nonexistent LOB value ] with root cause

java.sql.SQLException: ORA-22922: nonexistent LOB value

Antwort

3

Die Fehlermeldung der Oracle JDBC-Treiber, um anzuzeigen, erscheint doesn Ich weiß nicht, was Sie mit dem javax.sql.rowset.serial.SerialBlob-Objekt tun sollen, das Sie an dieses Objekt übergeben haben.

Versuchen Sie, die Blob Objekte mit Connection.createBlob stattdessen zu erstellen. Mit anderen Worten, versuchen Sie die folgenden Schleife

 for(int x = 0; x < byteFiles.size(); x++){ 
      fileBlobs.add(new javax.sql.rowset.serial.SerialBlob(byteFiles.get(x))); 
     } 

mit

 Connection conn = dataSource.getConnection(); 
     for(int x = 0; x < byteFiles.size(); x++){ 
      Blob blob = conn.createBlob(); 
      blob.setBytes(1, byteFiles.get(x)); 
      fileBlobs.add(blob); 
     } 

Auch ersetzen, stellen Sie sicher, dass Ihre Parameternamen zwischen Ihrem SimpleJdbcCall und Ihre gespeicherten Funktion konsistent sind. Ihr SimpleJdbcCall deklariert den Array-Parameter BLOB mit dem Namen p_data, aber Ihre gespeicherte Funktionsdeklaration verwendet p_files. Wenn die Parameternamen nicht konsistent sind, erhalten Sie wahrscheinlich einen Invalid column type Fehler.

Allerdings hatte ich den obigen Test mit einer gespeicherten Funktion von mir selbst ausgeführt, die tatsächlich etwas mit den BLOB Werte übergeben, statt nur einen Rückgabewert hart codieren, hätte ich gefunden, dass dieser Ansatz nicht Arbeit. Ich bin mir nicht sicher, warum, wahrscheinlich würde ich einige Zeit damit verbringen müssen, in den Eingeweiden von Spring zu graben, um es herauszufinden.

Ich habe versucht, die Blob Werte mit Spring SqlLobValue s zu ersetzen, aber das hat auch nicht funktioniert. Ich denke, Spring SqlArrayValue<T> Typ behandelt nicht Spring Wrapper-Objekte für verschiedene JDBC-Typen.

Also gab ich auf einem Frühlings-Ansatz auf und ging zurück zur Ebene JDBC:

import oracle.jdbc.OracleConnection; 

// ... 

     OracleConnection conn = dataSource.getConnection().unwrap(OracleConnection.class); 

     List<Blob> fileBlobs = new ArrayList<>(); 
     for(int x = 0; x < byteFiles.size(); x++){ 
      Blob blob = conn.createBlob(); 
      blob.setBytes(1, byteFiles.get(x)); 
      fileBlobs.add(blob); 
     } 

     Array array = conn.createOracleArray("BLOB_ARRAY", 
      fileBlobs.toArray(new Blob[fileBlobs.size()])); 

     CallableStatement cstmt = conn.prepareCall("{? = call insertFiles(?, ?)}"); 
     cstmt.registerOutParameter(1, Types.NUMERIC); 
     cstmt.setInt(2, userId); 
     cstmt.setArray(3, array); 

     cstmt.execute(); 

     int result = cstmt.getInt(1); 

ich diese mit der gespeicherten Funktion getestet haben Sie jetzt in Ihrer Frage aufgenommen haben, und es ist in der Lage zu nennen diese Funktion und fügen Sie die BLOB s in die Datenbank ein.

Ich überlasse es Ihnen, zu tun, was Sie mit der Variable result passen, und alle erforderlichen Bereinigung oder Transaktionssteuerung hinzuzufügen.

Allerdings, während dieser Ansatz arbeitete, fühlte es nicht rechts. Es passte nicht zu der Spring Art, Dinge zu tun. Es hat zumindest bewiesen, dass das, wonach Sie gefragt haben, möglich war, da es keine Einschränkung im JDBC-Treiber gab, die bedeutete, dass Sie BLOB Arrays nicht verwenden konnten. Ich hatte das Gefühl, dass es eine Möglichkeit geben sollte, Ihre Funktion mit Spring JDBC aufzurufen.

Ich verbrachte einige Zeit in den ORA-22922 Fehler und kam zu dem Schluss, dass das zugrunde liegende Problem war, dass die Blob Objekte mit einem anderen Connection erstellt wurden, was zur Ausführung der Anweisung verwendet wurde. Die Frage wird dann, wie man den Connection Spring nutzt.

Nach einigen weiteren Graben um im Quellcode zu verschiedenen Spring classes, erkennen ich, dass eine frühlingshafte Art und Weise, dies zu tun ist, um die SqlArrayValue<T> Klasse mit einem anderen für BLOB Arrays spezialisiert zu ersetzen. Dies ist, was ich am Ende mit:

import java.sql.Array; 
import java.sql.Blob; 
import java.sql.Connection; 
import java.sql.SQLException; 
import java.util.List; 

import oracle.jdbc.OracleConnection; 
import org.springframework.dao.InvalidDataAccessApiUsageException; 
import org.springframework.jdbc.core.support.AbstractSqlTypeValue; 

public class SqlBlobArrayValue extends AbstractSqlTypeValue { 

    private List<byte[]> values; 

    private String defaultTypeName; 

    public SqlBlobArrayValue(List<byte[]> values) { 
     this.values = values; 
    } 

    public SqlBlobArrayValue(List<byte[]> values, String defaultTypeName) { 
     this.values = values; 
     this.defaultTypeName = defaultTypeName; 
    } 

    protected Object createTypeValue(Connection conn, int sqlType, String typeName) 
      throws SQLException { 
     if (typeName == null && defaultTypeName == null) { 
      throw new InvalidDataAccessApiUsageException(
        "The typeName is null in this context. Consider setting the defaultTypeName."); 
     } 

     Blob[] blobs = new Blob[values.size()]; 
     for (int i = 0; i < blobs.length; ++i) { 
      Blob blob = conn.createBlob(); 
      blob.setBytes(1, values.get(i)); 
      blobs[i] = blob; 
     } 

     Array array = conn.unwrap(OracleConnection.class).createOracleArray(typeName != null ? typeName : defaultTypeName, blobs); 
     return array; 
    } 
} 

Diese Klasse ist stark auf SqlArrayValue<T>, die unter Version 2 of the Apache License lizenziert ist. Der Kürze halber habe ich Kommentare und eine package Direktive weggelassen.

Mit Hilfe dieser Klasse wird es viel einfacher, Ihre Funktion mit Spring JDBC aufzurufen. In der Tat können Sie alles nach dem Aufruf von uploadFiles.compile() mit folgenden ersetzen:

 SqlParameterSource in = new MapSqlParameterSource() 
       .addValue("p_files", new SqlBlobArrayValue(byteFiles, "BLOB_ARRAY")) 
       .addValue("p_userId", userId); 

     Map<String, Object> results = uploadFiles.execute(in); 
+0

danke für die Antwort, ich meine Frage aktualisiert. Ich habe anscheinend einen weiteren Fehler bei der Erstellung des 'BLOB' mit der' Connection' gefunden. Ich habe auch die Parameternamen konsistent gemacht. – Alan

+1

@Alan: Entschuldigung, dass Ihr Code immer noch einen Fehler zurückgibt. Das ist wahrscheinlich meine Schuld, weil ich mich nicht darum gekümmert habe, eine ordnungsgemäß gespeicherte Funktion zum Testen zu schreiben! Ich habe meine Antwort bearbeitet, um einen "einfachen" JDBC-Ansatz hinzuzufügen, der für mich funktioniert, indem ich damit Ihre Funktion aufrufen kann. –

+0

Danke! Ich habe meinen Kopf für ein paar Tage gegen dieses Problem geschlagen und habe versucht, es "Frühling" zu machen, und das scheint gut zu funktionieren. – Alan

Verwandte Themen