Java SpringJDBC-将blob数组传递给SQL函数

Java SpringJDBC-将blob数组传递给SQL函数,java,sql,spring,oracle,jdbc,Java,Sql,Spring,Oracle,Jdbc,我试图传入一个数组的BLOBs,但我得到了错误 uploadFiles = new SimpleJdbcCall(dataSource).withCatalogName("FILES_PKG") .withFunctionName("insertFiles").withReturnValue() .declareParameters(new SqlParameter("p_userId", Types.NUMERIC

我试图传入一个
数组
BLOB
s,但我得到了错误

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);
功能规格

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

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;
我不知道如何正确地将blob数组传递给SQL函数

错误:

java.sql.SQLException:无法转换为内部表示形式: javax.sql.rowset.serial。SerialBlob@87829c90在 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 toOracleArray(ArrayDescriptor.java:1321) ~[ojdbc7.jar:12.1.0.1.0]位于oracle.sql.ARRAY.(ARRAY.java:140) ~[ojdbc7.jar:12.1.0.1.0]at

更新

在尝试了Luke的建议后,我发现以下错误:

SQL[{?=call FILES_PKG.INSERTFILES(?)的未分类SQLException?, ?)}]; SQL状态[99999];错误代码[22922];ORA-22922:不存在 LOB值;嵌套异常为java.sql.SQLException:ORA-22922: 不存在具有根本原因的LOB值]

java.sql.SQLException:ORA-22922:不存在LOB值


出现的错误消息表明Oracle JDBC驱动程序不知道如何处理传递给它的javax.sql.rowset.serial.SerialBlob对象

尝试使用创建
Blob
对象。换句话说,尝试替换以下内容 环路

我已经使用您现在在问题中包含的存储函数对此进行了测试,它能够调用此函数并将
BLOB
s插入数据库

我将让您对变量
result
执行您认为合适的操作,并添加任何必要的清理或事务控制

然而,尽管这种方法有效,但感觉并不正确。它不适合春天的做事方式。它至少证明了您所要求的是可能的,因为JDBC驱动程序中没有一些限制,这意味着您不能使用
BLOB
数组。我觉得应该有办法使用SpringJDBC调用函数

我花了一些时间研究ORA-22922错误,并得出结论,根本问题在于
Blob
对象是使用与执行语句所用的连接不同的
来创建的。接下来的问题是如何获得Spring使用的
连接

在深入挖掘了各种Spring类的源代码之后,我意识到一种更像Spring的方法是将
SqlArrayValue
类替换为一个专门用于
BLOB
数组的类。这就是我的结局:

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

出现的错误消息表明Oracle JDBC驱动程序不知道如何处理传递给它的javax.sql.rowset.serial.SerialBlob对象

尝试使用创建
Blob
对象。换句话说,尝试替换以下内容 环路

我已经使用您现在在问题中包含的存储函数对此进行了测试,它能够调用此函数并将
BLOB
s插入数据库

我将让您对变量
result
执行您认为合适的操作,并添加任何必要的清理或事务控制

然而,尽管这种方法有效,但感觉并不正确。它不适合春天的做事方式。它至少证明了您所要求的是可能的,因为JDBC驱动程序中没有一些限制,这意味着您不能使用
BLOB
数组。我觉得应该有办法使用SpringJDBC调用函数

我花了一些时间研究ORA-22922错误,并得出结论,根本问题在于
Blob
对象是使用与执行语句所用的连接不同的
来创建的。接下来的问题是如何获得Spring使用的
连接

在深入挖掘了各种Spring类的源代码之后,我意识到一种更像Spring的方法是将
SqlArrayValue
类替换为一个专门用于
BLOB
数组的类。这就是我的结局:

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

谢谢你的回复,我更新了我的问题。在使用
连接创建
BLOB
时,我似乎遇到了另一个错误。我还使参数名保持一致。@Alan:很抱歉您的代码仍然返回错误。这可能是我的错,因为我没有费心编写一个适当的存储函数来进行测试!我已经编辑了我的答案,添加了一个“普通”的JDBC方法,这对我来说很有效,因为我可以使用它来调用您的函数。谢谢!几天来,我一直在为这个问题绞尽脑汁,并一直试图用“Spring”的方式来解决这个问题,但似乎效果很好。@Alan:如果您感兴趣,我已经设法想出了一种更像Spring的方法来调用您的函数。我编辑了我的答案,把它包括进去。太棒了!谢谢您的更新。:)我可能会用“弹性方式”结束谢谢你的回答,我更新了我的问题。在使用
连接创建
BLOB
时,我似乎遇到了另一个错误。我还使参数名保持一致。@Alan:很抱歉您的代码仍然返回错误。这可能是我的错,因为我没有费心编写一个适当的存储函数来进行测试!我已经编辑了我的答案,添加了一个“普通”的JDBC方法,这对我来说很有效,因为我可以使用它来调用您的函数。谢谢!几天来,我一直在为这个问题绞尽脑汁,并一直试图用“春天”的方式来解决它,而这似乎效果很好。@Alan:如果你有兴趣,我已经设法想出了一个更像春天的ap
        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);
        }
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);
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;
    }
}
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("p_files", new SqlBlobArrayValue(byteFiles, "BLOB_ARRAY"))
                .addValue("p_userId", userId);

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