java 使用JDBC调用以自定义记录作为IN参数的PL/SQL过程

b5buobof  于 2022-12-21  发布在  Java
关注(0)|答案(4)|浏览(220)

我正在尝试调用以下PL/SQL过程,该过程将用户定义的记录类型作为IN参数。

-- User Defined Record
   TYPE EMP_REC IS RECORD
   (
    id employees.employee_id%type,
    name employees.last_name%type,
    dept_name departments.department_name%type,
    job_title jobs.job_title%type,
    salary employees.salary%type,
    manager_id employees.employee_id%type,
    city locations.city%type,
    phone employees.phone_number%type
   );

下面是用户定义记录的定义:

-- PURPOSE: Prints all employee information from the employee record 
  -- Example Of: PROCEDURE that takes in a parameter of RECORD type 
  PROCEDURE print_employee_all_details(empl1 emp_rec , emp_rec_string OUT VARCHAR2)

我正在查看Oracle JDBC文档,该文档指出JDBC不支持复合类型,如RECORDS:

搜索互联网时转到this link
下面是我尝试将用户定义的记录传递给PL/SQL过程的代码:

public String printEmployeeAllDetails() {
    Connection conn = null;
    CallableStatement callStmt = null;
    String empDetails = null;

    try {
        // Register the Jdbc Driver
        // Class.forName(JDBC_DRIVER_ORACLE);

        // Create a Database Connection
        conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);

        // Create a query string
        String callProc = "{call HR.EMP_PKG.print_employee_all_details( ? , ?) }";

        // Create a Callable Statement
        callStmt = conn.prepareCall(callProc);

        // Create descriptor for the Oracle Record type "EMP_REC" required
        StructDescriptor recDescriptor = StructDescriptor.createDescriptor("EMP_REC", conn);

        // Stage values for each field in the Oracle record in an array
        Object[] javaEmpRec = new Object[8];

        // Populate those values in the Array
        javaEmpRec[0] = 100;
        javaEmpRec[1] = "Joe Matthew";
        javaEmpRec[2] = "IT";
        javaEmpRec[3] = "Senior Consultant";
        javaEmpRec[4] = 20000;
        javaEmpRec[5] = 101;
        javaEmpRec[6] = "lombard";
        javaEmpRec[7] = "222333444";

        // Cast the java array into the oracle record type
        STRUCT oracleEmpRec = new STRUCT(recDescriptor, conn, javaEmpRec);

        // Bind Values to the IN parameter
        callStmt.setObject(1, oracleEmpRec);

        // Register OUT parameter
        callStmt.registerOutParameter(2, java.sql.Types.VARCHAR);

        // Execute the Callable Statement
        callStmt.execute();

        // Retrieve the value from the OUT parameter
        empDetails = callStmt.getString(2);
        System.out.println("Emp Details: " + empDetails);

    } catch (SQLException se) {
        System.out.println("Exception occured in the database");
        System.out.println("Exception message: " + se.getMessage());
        System.out.println("Database error code: " + se.getErrorCode());
        se.printStackTrace();
    } finally {
        // Clean up
        if (callStmt != null) {
            try {
                callStmt.close();
            } catch (SQLException se2) {
                se2.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException se2) {
                se2.printStackTrace();
            }
        }
    }

    return empDetails;
}

在运行这段代码时,我得到了以下异常:

Exception occured in the database
Exception message: invalid name pattern: HR.EMP_REC
java.sql.SQLException: invalid name pattern: HR.EMP_REC
    at oracle.jdbc.oracore.OracleTypeADT.initMetadata(OracleTypeADT.java:554)
    at oracle.jdbc.oracore.OracleTypeADT.init(OracleTypeADT.java:471)
    at oracle.sql.StructDescriptor.initPickler(StructDescriptor.java:324)
    at oracle.sql.StructDescriptor.<init>(StructDescriptor.java:254)
    at oracle.sql.StructDescriptor.createDescriptor(StructDescriptor.java:135)
    at oracle.sql.StructDescriptor.createDescriptor(StructDescriptor.java:103)
Database error code: 17074
    at oracle.sql.StructDescriptor.createDescriptor(StructDescriptor.java:72)
    at com.rolta.HrManager.printEmployeeAllDetails(HrManager.java:1214)
    at com.rolta.HrManager.main(HrManager.java:1334)

我正在使用ojdbc6.jar,这是JDBC Thin for All Platforms标题下的第一个jar,用于Oracle Database 11g Release 2 (11.2.0.4) JDBC Driverson this page

我想知道是否允许将用户定义的记录(作为IN参数)传递到PL/SQL过程?有人尝试过以上操作吗?

5w9g7ksd

5w9g7ksd1#

是的,允许使用JDBC将用户定义的数据类型作为IN参数传递。但是它不能是RECORD。它必须是一个模式级对象,例如。

CREATE TYPE EMP_REC AS OBJECT
(
 id employees.employee_id%type,
 name employees.last_name%type,
 dept_name departments.department_name%type,
 job_title jobs.job_title%type,
 salary employees.salary%type,
 manager_id employees.employee_id%type,
 city locations.city%type,
 phone employees.phone_number%type
);

在PL/SQL中,您可以将记录的引用更改为新的对象类型,或者,如果无法更改其余代码,则可以编写一个快速的小转换器函数,将对象类型转换为记录类型。

rks48beu

rks48beu2#

如果您可以执行匿名PL/SQL块(据我所知,这是可能的),您可以执行以下操作:

declare
  rec EMP_REC;
begin
  rec.id := :ID;
  rec.name:= :NAME;
  -- and so on, rest of fields of record...
  ...
  my_procedure(rec);
end;
/

在这种情况下,您不需要创建新的数据库对象或更改现有的数据库对象,只需要传递参数值来填充记录。

7uzetpgm

7uzetpgm3#

我想补充一下Dmitry's answer,它建议您可以通过JDBC使用匿名PL/SQL块,并手动显式地组合RECORD类型。如果您正在为单个存储过程寻找解决方案,那么手动编写该块就可以了。但是如果您正在寻找一个 * 通用解决方案 *,为 * 所有 * 具有INOUT、或IN OUTRECORD参数,您可能应该编写一个代码生成器,根据以下查询生成存根

SELECT
  x.TYPE_OWNER, x.TYPE_NAME, x.TYPE_SUBNAME, a.ARGUMENT_NAME ATTR_NAME,
  a.SEQUENCE ATTR_NO, a.TYPE_OWNER ATTR_TYPE_OWNER,
  nvl2(a.TYPE_SUBNAME, a.TYPE_NAME, NULL) package_name,
  COALESCE(a.TYPE_SUBNAME, a.TYPE_NAME, a.DATA_TYPE) ATTR_TYPE_NAME,
  a.DATA_LENGTH LENGTH, a.DATA_PRECISION PRECISION, a.DATA_SCALE SCALE
FROM SYS.ALL_ARGUMENTS a
JOIN (
  SELECT
    a.TYPE_OWNER, a.TYPE_NAME, a.TYPE_SUBNAME,
    MIN(a.OWNER) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) OWNER,
    MIN(a.PACKAGE_NAME) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) PACKAGE_NAME,
    MIN(a.SUBPROGRAM_ID) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) SUBPROGRAM_ID,
    MIN(a.SEQUENCE) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) SEQUENCE,
    MIN(next_sibling) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) next_sibling,
    MIN(a.DATA_LEVEL) KEEP (DENSE_RANK FIRST ORDER BY a.OWNER ASC, a.PACKAGE_NAME ASC, a.SUBPROGRAM_ID ASC, a.SEQUENCE ASC) DATA_LEVEL
  FROM (
    SELECT
      lead(a.SEQUENCE, 1, a.SEQUENCE) OVER (
        PARTITION BY a.OWNER, a.PACKAGE_NAME, a.SUBPROGRAM_ID, a.DATA_LEVEL
        ORDER BY a.SEQUENCE ASC
      ) next_sibling,
      a.TYPE_OWNER, a.TYPE_NAME, a.TYPE_SUBNAME, a.OWNER, a.PACKAGE_NAME, 
      a.SUBPROGRAM_ID, a.SEQUENCE, a.DATA_LEVEL, a.DATA_TYPE
    FROM SYS.ALL_ARGUMENTS a
    WHERE a.OWNER IN ('MY_SCHEMA')     -- Possibly replace schema here
    ) a
  WHERE (a.TYPE_OWNER IN ('MY_SCHEMA') -- Possibly replace schema here
  AND a.OWNER         IN ('MY_SCHEMA') -- Possibly replace schema here
  AND a.DATA_TYPE      = 'PL/SQL RECORD')
  GROUP BY a.TYPE_OWNER, a.TYPE_NAME, a.TYPE_SUBNAME
  ) x
ON ((a.OWNER, a.PACKAGE_NAME, a.SUBPROGRAM_ID) = ((x.OWNER, x.PACKAGE_NAME, x.SUBPROGRAM_ID))
AND a.SEQUENCE BETWEEN x.SEQUENCE AND next_sibling
AND a.DATA_LEVEL = (x.DATA_LEVEL + 1))
ORDER BY x.TYPE_OWNER ASC, x.TYPE_NAME ASC, x.TYPE_SUBNAME ASC, a.SEQUENCE ASC

这将为您提供MY_SCHEMA模式中包含的所有包中的所有RECORD类型的正式定义,从中您可以生成类似于Dmitry答案中的存根:

declare
  rec EMP_REC;
begin
  rec.id := :ID;
  rec.name:= :NAME;
  -- and so on, rest of fields of record...
  ...
  my_procedure(rec);
end;
/

See more details about this technique in this blog post (from which the query was taken).

vfh0ocws

vfh0ocws4#

无法传递记录,因为必须是SQL对象才能引用,而不是纯PL/SQL对象。
创建一个对象就像,

-- User Defined Record
   CREATE TYPE EMP_REC AS OBJECT
   (
    id  NUMBER,
    name VARCHAR2(100),
    dept_name ...,
    job_title ..,
    salary ..,
    manager_id ..,
    city ..,
    phone ...
   );

这也是一个痛苦。你不能在这里使用TYPE属性。因为TYPE不能有这样的依赖关系。而是指定确切的数据类型。

相关问题