[英]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:
一些观察:
Struct
which was counter-intuitive (as only arrays were used for outputs).Struct
这是反直觉的(因为只有阵列,用于输出)。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()
。 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.