繁体   English   中英

Spring JDBC-将BLOB的ARRAY传递给SQL函数

[英]Spring JDBC - Passing in ARRAY of BLOBs to SQL Function

我正在尝试传递BLOBARRAY ,但出现错误。

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

我在数据库中创建了一个SQL类型

create or replace TYPE BLOB_ARRAY is table of BLOB;

功能规格

 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]在oracle.jdbc.oracore.OracleType.toDatumArray(OracleType.java:176)〜[ojdbc7.jar:12.1.0.1.0]在oracle.sql.ArrayDescriptor.toOracleArray(ArrayDescriptor.java:1321)〜[ojdbc7 .jar:12.1.0.1.0],位于oracle.sql.ARRAY。(ARRAY.java:140)〜[ojdbc7.jar:12.1.0.1.0],位于

UPDATE

尝试了卢克的建议后,出现以下错误:

未分类的SQLException for SQL [{? =调用FILES_PKG.INSERTFILES(?,?)}]; 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对象。

尝试改用Connection.createBlob创建Blob对象。 换句话说,尝试替换以下循环

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

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

另外,请确保您的SimpleJdbcCall和存储的函数之间的参数名称一致。 SimpleJdbcCall声明BLOB名称为数组参数p_data但你的存储功能的声明使用p_files 如果参数名称不一致,则很可能会收到Invalid column type错误。

但是,如果我使用自己的存储函数运行了上述测试,该存储函数实际上对传入的BLOB值做了一些事情,而不仅仅是对返回值进行硬编码,那么我可能会发现这种方法行不通。 我不确定为什么,我可能不得不花一些时间在Spring的内心中发掘出来,以找出答案。

我尝试用Spring SqlLobValue替换Blob值,但这也不起作用。 我猜想Spring的SqlArrayValue<T>类型不能处理各种JDBC类型的Spring包装器对象。

所以我放弃了Spring方法,回到了普通的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);

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

我将根据您的意愿来处理变量result并添加任何必要的清理或事务控制权。

但是,尽管这种方法有效 ,但感觉并不正确 它不适合春季的工作方式。 它至少证明了您所要求的是可能的,因为JDBC驱动程序没有任何限制,这意味着您不能使用BLOB数组。 我觉得应该有某种使用Spring JDBC调用函数的方法。

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

在源代码中进一步深入到各种Spring类之后,我意识到一种更像Spring的方式是用另一种专门用于BLOB数组的类替换SqlArrayValue<T>类。 我最终得到的是:

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

该类很大程度上基于SqlArrayValue<T> ,后者根据Apache License的版本2获得许可 为简便起见,我省略了注释和package指令。

在此类的帮助下,使用Spring JDBC调用函数变得容易得多。 实际上,您可以在调用uploadFiles.compile()之后将所有内容替换为以下内容:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM