【问题标题】:Spring JDBC - Passing in ARRAY of BLOBs to SQL FunctionSpring JDBC - 将 BLOB 的数组传递给 SQL 函数
【发布时间】:2016-06-17 21:39:44
【问题描述】:

我正在尝试传递BLOBs 中的ARRAY,但我遇到了错误。

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] 在

更新

尝试卢克的建议后,我收到以下错误:

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

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

【问题讨论】:

    标签: java sql spring oracle jdbc


    【解决方案1】:

    错误消息似乎表明 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 声明了名称为p_dataBLOB 数组参数,但您的存储函数声明使用p_files。如果参数名称不一致,您可能会收到Invalid column type 错误。

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

    我尝试用 Spring SqlLobValues 替换 Blob 值,但这也不起作用。我猜 Spring 的 SqlArrayValue&lt;T&gt; 类型不能处理各种 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);
    

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

    我将由您来做您认为适合变量 result 的事情,并添加任何必要的清理或事务控制。

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

    我花了一些时间研究 ORA-22922 错误并得出结论,根本问题是 Blob 对象是使用与执行语句不同的 Connection 创建的。那么问题就变成了如何掌握Connection Spring 的用途。

    在对各种 Spring 类的源代码进行了进一步挖掘之后,我意识到一种更类似于 Spring 的方法是将 SqlArrayValue&lt;T&gt; 类替换为专门用于 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;
        }
    }
    

    这个类在很大程度上基于SqlArrayValue&lt;T&gt;,它在Version 2 of the Apache License 下获得许可。为简洁起见,我省略了 cmets 和 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);
    

    【讨论】:

    • 感谢您的回复,我更新了我的问题。在使用Connection 创建BLOB 时,我似乎遇到了另一个错误。我还使参数名称保持一致。
    • @Alan:很抱歉您的代码仍然返回错误。这可能是我没有费心编写适当的存储函数来测试的错!我已经编辑了我的答案,添加了一个对我有用的“普通”JDBC 方法,因为我可以用它来调用你的函数。
    • 谢谢!几天来,我一直在努力解决这个问题,并一直试图以“春天”的方式来解决这个问题,这似乎工作得很好。
    • @Alan:如果您有兴趣,我已经设法想出了一种更类似于 Spring 的方法来调用您的函数。我已经编辑了我的答案以包含它。
    • 太棒了!谢谢你的更新。 :) 我可能最终会使用 Springy 方式
    猜你喜欢
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 2020-07-28
    • 1970-01-01
    相关资源
    最近更新 更多