简体   繁体   English

如何将用户定义的类型作为输入传递给存储过程?

[英]How do I pass a user-defined type as an input to a stored procedure?

I have two related stored procedures that involve user-defined types.我有两个涉及用户定义类型的相关存储过程。 The first accepts an object ID and returns the corresponding instance of a user-defined type.第一个接受对象 ID 并返回用户定义类型的相应实例。 The second accepts an instance of the same user-defined type and does something with it.第二个接受相同用户定义类型的实例并用它做一些事情。

I am using Java, JDBC and a little bit of Spring JDBC.我正在使用 Java、JDBC 和一些 Spring JDBC。 I have successfully completed the first stored procedure, ie.我已经成功完成了第一个存储过程,即。 I can retrieve instances of my user-defined type from the DB, However, I cannot get the second stored procedure to work.我可以从数据库中检索我的用户定义类型的实例,但是,我无法让第二个存储过程工作。

Here's the basic outline of what I have so far:这是我到目前为止的基本轮廓:

Schema (PL/SQL)架构 (PL/SQL)

create or replace type example_obj as object
  (ID     NUMBER,
   NAME   VARCHAR2(100))

create or replace type example_tab as table of example_obj

create or replace package
example as

procedure getExample
(p_id      in number,
 p_example out example_tab);

procedure useExample
(p_example  in example_tab);

end example;

Entity (Java) - represents the user-defined type in Java实体 (Java) - 表示 Java 中的用户定义类型

public class Example {
    public BigDecimal ID;
    public String Name;
}

Mapper (Java) - maps from the SQL type to the Java type and back Mapper (Java) - 从 SQL 类型映射到 Java 类型并返回

public class ExampleMapper extends Example implements SQLData {
    public static final String SQL_OBJECT_TYPE_NAME = "example_obj";
    public static final String SQL_TABLE_TYPE_NAME  = "example_tab";    

    @Override
    public String getSQLTypeName() throws SQLException {
        return SQL_TABLE_TYPE_NAME;
    }

    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException  {
        ID   = stream.readBigDecimal();
        Name = stream.readString();
    }

    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeBigDecimal(ID);
        stream.writeString(Name);
    }
}

First Stored Procedure (Java) - retrieves an Example object given its ID第一个存储过程 (Java) - 检索给定 ID 的 Example 对象

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public Example getExample(BigDecimal ID) throws SQLException {
    String query = "begin example.getExample(?, ?); end;";
    Connection connection = jdbcTemplate.getDataSource().getConnection();
    CallableStatement callableStatement = connection.prepareCall(query);

    callableStatement.setBigDecimal("p_id", ID);

    Map<String, Class<?>> typeMap = connection.getTypeMap();
    typeMap.put(Example.SQL_OBJECT_TYPE_NAME, ExampleMapper.class);
    callableStatement.registerOutParameter("p_example", Types.ARRAY, Example.SQL_TABLE_TYPE_NAME);
    connection.setTypeMap(typeMap);

    callableStatement.execute();

    Array array = (Array)callableStatement.getObject("p_example");
    Object[] data = (Object[])array.getArray();
    Example example = (Example)data[0]; // It's an ExampleMapper, but I only want Example
    return example;
}

As I noted earlier, the first stored procedure is working correctly.正如我之前提到的,第一个存储过程工作正常。 The object retrieved from the database is automagically mapped into the corresponding Java object.从数据库中检索到的对象会自动映射到相应的 Java 对象中。 The next step is to be able to call the stored procedure that accepts an instance of this user-defined type.下一步是能够调用接受此用户定义类型的实例的存储过程。

Second Stored Procedure (Java) - uses an Example object - incomplete第二个存储过程 (Java) - 使用 Example 对象 - 不完整

public void useExample(Example example) throws SQLException {
    String query = "begin example.useExample(?); end;";
    Connection connection = jdbcTemplate.getDataSource().getConnection();
    CallableStatement callableStatement = connection.prepareCall(query);

    // Is this required (as per getExample())?
    Map<String, Class<?>> typeMap = connection.getTypeMap();
    typeMap.put(Example.SQL_OBJECT_TYPE_NAME, ExampleMapper.class);
    connection.setTypeMap(typeMap);

    /***
     *** What goes here to pass the object in as a parameter?
     ***/
    callableStatement.setObject("p_example", ???);

    callableStatement.execute();
}

After a fair bit of mucking around, I was able to develop a solution.经过一番折腾,我终于找到了解决方案。 A few observations:一些观察:

  • There is not much documentation about how to do this on the web.关于如何在网络上执行此操作的文档并不多。
  • It seems to me that using user-defined types as inputs is not well supported.在我看来,使用用户定义的类型作为输入并没有得到很好的支持。
  • I found I had to use a Struct which was counter-intuitive (as only arrays were used for outputs).我发现我不得不使用Struct这是反直觉的(因为只有阵列,用于输出)。
  • The SQLData interface was not used, ie.未使用SQLData接口,即。 writeSQL() was never called as I found I had to build the struct manually. writeSQL()从未被调用,因为我发现我必须手动构建结构。 readSQL() is called when mapping outputs.映射输出时调用readSQL()
  • I had to use DB-specific code for array creation, in my case this meant Oracle classes.我必须使用特定于数据库的代码来创建数组,在我的情况下,这意味着 Oracle 类。

It's possible I may be going about things the wrong way, so I'd welcome comments on my solution.我可能会以错误的方式处理事情,所以我欢迎对我的解决方案发表评论。

public void useExample(Example example) throws SQLException {
    String query = "begin example.useExample(?); end;";
    Connection connection = jdbcTemplate.getDataSource().getConnection();
    CallableStatement callableStatement = connection.prepareCall(query);

    Map<String, Class<?>> typeMap = connection.getTypeMap();
    typeMap.put(Example.SQL_OBJECT_TYPE_NAME, ExampleMapper.class);
    connection.setTypeMap(typeMap);

    // Manually convert the example object into an SQL type.
    Object[] exampleAttributes = new Object[]{example.ID, example.Name};
    Struct struct = connection.createStruct(type.getObjectType(), exampleAttributes);

    // Build the array using Oracle specific code.
    DelegatingConnection<OracleConnection> delegatingConnection = (DelegatingConnection<OracleConnection>) new DelegatingConnection(connection);
    OracleConnection oracleConnection = (OracleConnection) delegatingConnection.getInnermostDelegate();
    Object[] data = new Object[]{struct};
    Array array oracleConnection.createOracleArray(Example.SQL_TABLE_TYPE_NAME, data);

    // Set the input value (finally).
    callableStatement.setObject("p_example", array);

    callableStatement.execute();
}

You don't have to manually convert the data.您不必手动转换数据。 And you definitely don't need Struct.而且你绝对不需要Struct。 Here is the simplified version:这是简化版:

final OracleConnection oracleConnection = (OracleConnection) connection.getClass().
    getMethod("getUnderlyingConnection").invoke(connection);

List<Example> example = new ArrayList<>();
example.add(new Example(1L, "something"));
example.add(new Example(2L, "something else"));

Map<String, Class<?>> typeMap = connection.getTypeMap();
typeMap.put(Example.SQL_OBJECT_TYPE_NAME, Example.class);
connection.setTypeMap(typeMap);

Array array = oracleConnection.createOracleArray(Example.SQL_TABLE_TYPE_NAME, example.toArray());

statement.setObject(1, array);
statement.execute();

Note that I combined the Example and ExampleMapper into one class (for simplicity).请注意,我将 Example 和 ExampleMapper 合并为一个类(为简单起见)。 What was also wrong in your example is this:您的示例中还有什么问题:

@Override
public String getSQLTypeName() throws SQLException {
    return SQL_OBJECT_TYPE_NAME;
}

As you can see, this overridden method must return the object type name, not the table type name.如您所见,这个被覆盖的方法必须返回对象类型名称,而不是表类型名称。

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

相关问题 使用 VARRAY 或用户定义类型作为 IN 参数的 Oracle 存储函数/过程 - Oracle stored function/procedure with VARRAY or user-defined type as IN parameter 如何在java中调用包含用户定义类型的oracle存储过程? - How to call oracle stored procedure which include user-defined type in java? 使用 JDBC 将用户定义的表类型传递给 SQL Server 存储过程 - Passing a user-defined table type to a SQL Server stored procedure using JDBC 如何在Android中将用户定义的数组参数传递给asynctask - how do i pass user-defined array parameter to asynctask in android 如何创建用户定义的JLabel,JTextField? - How do I create user-defined JLabel,JTextField? 如何在 java 中返回用户定义的 object? - How do I return a user-defined object in java? 如何用用户定义的对象填充数组? - How do I fill an array with user-defined objects? 如何将通用类型转换为用户定义的类型? - How does one cast a generic type in to a user-defined type? 当LinkedList为用户定义对象类型时,如何在Java中的LinkedList中进行迭代? - How do we iterate in a LinkedList in java when LinkedList is of type user-defined objects? SQL4306N Java 存储过程或用户定义函数无法调用 Java 方法 - SQL4306N Java stored procedure or user-defined function could not call Java method
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM