【问题标题】:How to convert Oracle user defined Type into java object in spring jdbc stored procedure如何在spring jdbc存储过程中将Oracle用户定义的类型转换为java对象
【发布时间】:2017-03-02 22:39:32
【问题描述】:

我正在研究springjdbcTemplate,所有的db调用都将通过存储过程完成。在 Oracle 11g 中,我创建了一个用户定义类型,其中包含其他类型作为字段,如下所示。

create or replace type WORKER AS Object (NAME VARCHAR2(30),
                                         age NUMBER);

create or replace type WORKER_LIST IS TABLE OF WORKER;

create or replace type MANAGER AS Object(
NAME VARCHAR2(30),
workers WORKER_LIST
);

在 Java 方面,我创建了如下类。

public class Worker implements SQLData {
    private String name;
    private int age;
    @Override
    public String getSQLTypeName() throws SQLException {
        return "WORKER";
    }
    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        setName(stream.readString());
        setAge(stream.readInt());

    }
    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeString(getName());
        stream.writeInt(getAge());
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

public class Manager implements SQLData  {

    private String name;
    private List<Worker> workers;

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

    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        setName(stream.readString());
        setWorkers((List<Worker>) stream.readObject());
    }

    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeString(getName());
        stream.writeObject((SQLData) getWorkers());
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Worker> getWorkers() {
        return workers;
    }

    public void setWorkers(List<Worker> workers) {
            this.workers = workers;
        }
    }

我在 typeMap 中提到过映射。

但我没有得到预期的结果。

Worker 类型以 Struct 形式返回,List&lt;Worker&gt; 以数组形式返回。

请让我知道我应该做什么以及获取我上面提到的预期对象的标准协议是什么。我是 JDBCTemplate 的新手。请提出建议。

谢谢 拉姆

【问题讨论】:

  • Manager 类中getSQLTypeName() 返回的值从"Manager" 更改为"MANAGER" 是否有帮助?
  • 这是我的实际代码的副本,但在我的实际代码中,它只是正确的 SQL 类型的名称。
  • 您好,您可以分享任何解决此类问题的文章和博客。

标签: spring oracle stored-procedures jdbc types


【解决方案1】:

我想我已经设法让某些东西发挥作用了。

您提到了连接的类型映射。使用 Spring 时,很难掌握数据库连接以便将类型添加到连接的类型映射中,所以当你写“我在 typeMap 中提到过映射”时,我不确定你的意思。

Spring 提供了一种向连接类型映射添加条目的方法,形式为 SqlReturnSqlData 类。这可用于调用返回用户定义类型的存储过程或函数。它在连接的类型映射中添加一个条目,以指定对象的数据库类型和将此对象映射到的类,就在它从CallableStatement 检索值之前。但是,这仅在您只需要映射单个类型时才有效。您有两种需要映射的类型:MANAGERWORKER

幸运的是,想出一个替换 SqlReturnSqlData 的方法并不难,它可以在连接的类型映射中添加多个条目:

import org.springframework.jdbc.core.SqlReturnType;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;

public class SqlReturnSqlDataWithAuxiliaryTypes implements SqlReturnType {

    private Class<?> targetClass;

    private Map<String, Class<?>> auxiliaryTypes;

    public SqlReturnSqlDataWithAuxiliaryTypes(Class<?> targetClass, Map<String, Class<?>> auxiliaryTypes) {
        this.targetClass = targetClass;
        this.auxiliaryTypes = auxiliaryTypes;
    }

    @Override
    public Object getTypeValue(CallableStatement cs, int paramIndex, int sqlType, String typeName) throws SQLException {
        Connection con = cs.getConnection();
        Map<String, Class<?>> typeMap = con.getTypeMap();
        typeMap.put(typeName, this.targetClass);
        typeMap.putAll(auxiliaryTypes);
        Object o = cs.getObject(paramIndex);
        return o;
    }
}

以上已经改编自SqlReturnSqlData的源码。我真正做的只是添加了一个额外的字段auxiliaryTypes,其内容在对getTypeValue() 的调用中被添加到连接的类型映射中。

我还需要调整Manager 类的readSQL 方法。您从流中读回的对象将是java.sql.Array 的实现。您不能只将其投射到列表中。可悲的是,把它弄出来有点繁琐:

    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        setName(stream.readString());
        Array array = (Array) stream.readObject();
        Object[] objects = (Object[]) array.getArray();
        List<Worker> workers = Arrays.stream(objects).map(o -> (Worker)o).collect(toList());
        setWorkers(workers);
    }

(如果您不使用 Java 8,请将带有 Arrays.stream(...) 的行替换为循环。)

为了测试这一点,我编写了一个简短的存储函数来返回一个 MANAGER 对象:

CREATE OR REPLACE FUNCTION f_get_manager
RETURN manager
AS
BEGIN
  RETURN manager('Big Boss Man', worker_list(worker('Bill', 40), worker('Fred', 36)));
END;
/

调用这个存储函数的代码如下:

    Map<String, Class<?>> auxiliaryTypes = Collections.singletonMap("WORKER", Worker.class);
    SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
        .withSchemaName("my_schema")
        .withFunctionName("f_get_manager")
        .declareParameters(
            new SqlOutParameter(
                "return",
                OracleTypes.STRUCT,
                "MANAGER",
                new SqlReturnSqlDataWithAuxiliaryTypes(Manager.class, auxiliaryTypes)));

    Manager manager = jdbcCall.executeFunction(Manager.class);
    // ... do something with manager.

这很有效,因为它返回了一个 Manager 对象,其中包含两个 Workers。


最后,如果您有将Manager 对象保存到数据库的存储过程,请注意您的Manager 类的writeSQL 方法将不起作用。除非您编写了自己的List 实现,否则List&lt;Worker&gt; 不能转换为SQLData。相反,您需要创建一个 Oracle 数组对象并将条目放入其中。然而,这很尴尬,因为您需要数据库连接来创建数组,但这在 writeSQL 方法中不可用。请参阅this question 了解一种可能的解决方案。

【讨论】:

  • 谢谢卢克。您的解决方案对我有用,只需稍作改动。非常感谢。
猜你喜欢
  • 2011-04-07
  • 2017-06-08
  • 1970-01-01
  • 2015-09-21
  • 1970-01-01
  • 2015-10-03
  • 1970-01-01
  • 2014-04-11
  • 2012-09-05
相关资源
最近更新 更多