如何将用户定义的类型作为输入传递给存储过程?
Posted
技术标签:
【中文标题】如何将用户定义的类型作为输入传递给存储过程?【英文标题】:How do I pass a user-defined type as an input to a stored procedure? 【发布时间】:2018-02-14 14:33:27 【问题描述】:我有两个涉及用户定义类型的相关存储过程。第一个接受对象 ID 并返回用户定义类型的相应实例。第二个接受相同用户定义类型的实例并对其进行处理。
我正在使用 Java、JDBC 和一点点 Spring JDBC。我已经成功完成了第一个存储过程,即。我可以从数据库中检索我的用户定义类型的实例,但是,我无法让第二个存储过程工作。
这是我目前所拥有的基本大纲:
架构 (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) - 代表Java中用户定义的类型
public class Example
public BigDecimal ID;
public String Name;
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);
第一个存储过程 (Java) - 检索给定 ID 的示例对象
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;
如前所述,第一个存储过程工作正常。从数据库中检索到的对象会自动映射到相应的 Java 对象中。下一步是能够调用接受此用户定义类型的实例的存储过程。
第二个存储过程 (Java) - 使用示例对象 - 不完整
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();
【问题讨论】:
【参考方案1】:您不必手动转换数据。而且你绝对不需要 Struct。这是简化版:
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();
请注意,我将 Example 和 ExampleMapper 合并为一个类(为简单起见)。您的示例中也存在以下问题:
@Override
public String getSQLTypeName() throws SQLException
return SQL_OBJECT_TYPE_NAME;
如您所见,这个被覆盖的方法必须返回对象类型名称,而不是表类型名称。
【讨论】:
【参考方案2】:经过一番折腾,我能够开发出一个解决方案。几点观察:
关于如何在网络上执行此操作的文档并不多。 在我看来,使用用户定义的类型作为 输入 并没有得到很好的支持。 我发现我必须使用Struct
,这是违反直觉的(因为只有数组用于输出)。
SQLData
接口未被使用,即。 writeSQL()
从未被调用,因为我发现我必须手动构建结构。映射输出时调用readSQL()
。
我必须使用特定于 DB 的代码来创建数组,在我的例子中,这意味着 Oracle 类。
我可能会以错误的方式处理事情,所以我欢迎 cmets 在我的解决方案中。
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();
【讨论】:
以上是关于如何将用户定义的类型作为输入传递给存储过程?的主要内容,如果未能解决你的问题,请参考以下文章
使用 JDBC 将用户定义的表类型传递给 SQL Server 存储过程