java调用oracle存储过程/函数。参数为复杂类型(数组,自定义类型)

对于存储过程/函数入参,或出参为复杂类型的情况,这里列出官方的参考实践。有时间再补充。

1.出参为数组的情况:

16.4.3.2 Passing an Array to a Callable Statement

To retrieve a collection as an OUT parameter in PL/SQL blocks, execute the following to register the bind type for your OUT parameter.

  1. Cast your callable statement to an OracleCallableStatement:

    OracleCallableStatement ocs = 
       (OracleCallableStatement)conn.prepareCall("{? = call func()}");
    
    
  2. Register the OUT parameter with this form of the registerOutParameter() method:

    ocs.registerOutParameter
            (int param_index, int sql_type, string sql_type_name);
    //这里sql_type_name为我们自定义的type类型的名称。如MY_TYPE.
    //(CREATE OR REPLACE TYPE MY_TYPE AS VARRAY(2) of integer).

    Where param_index is the parameter index, sql_type is the SQL typecode, and sql_type_name is the name of the array type. In this case, the sql_type is OracleTypes.ARRAY.

  3. Execute the call:

    ocs.execute();
    
    
  4. Get the value:

    oracle.sql.ARRAY array = ocs.getARRAY(1);//1为数组参数在参数列表中的序号(从1开始)
    

  int[] intArray = array.getIntArray();//转为java数组
  System.out.println("array(1):"+intArray[0]);

2.出参为自定义类型(对象)的情况:

13.3.5.2 Retrieving SQLData Objects from a Callable Statement OUT Parameter

Suppose you have an OracleCallableStatement ocs that calls a PL/SQL function GETEMPLOYEE(). The program passes an employee number (empnumber) to the function; the function returns the corresponding Employee object.

  1. Prepare an OracleCallableStatement to call the GETEMPLOYEE() function.

    OracleCallableStatement ocs = 
      (OracleCallableStatement)conn.prepareCall("{ ? = call GETEMPLOYEE(?) }"); 
    
  1. Declare the empnumber as the input parameter to GETEMPLOYEE(). Register the SQLData object as the OUT parameter, with typecode OracleTypes.STRUCT. Then, execute the statement.

    ocs.setInt(2, empnumber); 
    ocs.registerOutParameter(1, OracleTypes.STRUCT, "EMP_OBJECT"); 
    ocs.execute(); 
    
    
  2. Use the getObject() method to retrieve the employee object. The following code assumes that there is a type map entry to map the Oracle object to Java type Employee:

    Employee emp = (Employee)ocs.getObject(1); 
    
    

    If there is no type map entry, then getObject() would return an oracle.sql.STRUCT object. Cast the output to type STRUCT, because the getObject() method signature returns the generic java.lang.Object type:

    STRUCT emp = (STRUCT)ocs.getObject(1); 

文档参考:

Oracle® Database JDBC Developer's Guide and Reference
10g Release 1 (10.1)

Part Number B10979-02

章节: 13 Working with Oracle Object Types

章节: 16 Working with Oracle Collections

今天有时间翻开以前的日志,下面附上我的实现摘要(2013.03.01):
数据库部分:
1.包声明:

create or replace package KP_CFCS4 is
  --改签原票汇总合计
  procedure get_cs4_resign_sum(where_sql IN string, sum_cs4 OUT KS_CS4SUM);
end KP_CFCS4;

2.包体:

create or replace package body KP_CFCS4 is
/*
   * 改签原票汇总合计
   */
  procedure get_cs4_resign_sum(where_sql IN string, sum_cs4 OUT KS_CS4SUM) is
    sqlStr varchar2(500);
  begin
    sum_cs4 := KS_CS4SUM(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
    /*770    合计    "669,191.50"                --改签原票的金额合计
    980    电子支付银行卡张数    "1,245.00"       --POS机张数、乘车凭证张数两项之和
    981    现金总票价    "459,655.00"             --合计中的现金部分
    983    银行卡总票价    "11,782.50"           --合计中的POS机部分
    984    乘车证总票价    "197,754.00"          --合计中的乘车凭证部分(打票部分)
    993    现金净退款    "459,655.00"             --已取消改签手续费,金额同“现金总票价”。
    995    银行卡净退款    "11,782.50"           --已取消改签手续费,金额同“银行卡总票价”。
    996    乘车证净退款    "197,754.00"*/        --已取消改签手续费,金额同“乘车证总票价”。
    sqlStr := 'select sum( kppjzs0 * kpfyhj0 ),'||
              '       sum( kpposzs + kpdpzs0 ),'||
              '       sum( kprmbzs * kpfyhj0 ),'||
              '       sum( kpposzs * kpfyhj0 ),'||
              '       sum( kpdpzs0 * kpfyhj0 ) '||
              ' from PTCHECK.AKP_SHCCZB0 t '|| where_sql;
    sqlStr := sqlStr||' and t.kpfpbj0=''9''';      --增加改签原票条件
    execute immediate sqlStr INTO sum_cs4.sum_all_amount,
                                  sum_cs4.sum_card_ticket_num,
                                  sum_cs4.sum_cash_amount,
                                  sum_cs4.sum_bank_card_amount,
                                  sum_cs4.sum_voucher_amount;
    
    --计算合计:改签原票的金额合计 = 现金总票价 + 银行卡总票价 + 乘车证总票价。
    --采用加法,避免前台页面数据不对
    sum_cs4.sum_all_amount := sum_cs4.sum_cash_amount + sum_cs4.sum_bank_card_amount + sum_cs4.sum_voucher_amount;
    
    --净退款(金额同各“xx总票价)
    sum_cs4.sum_cash_return_amount := sum_cs4.sum_cash_amount;
    sum_cs4.sum_bank_card_return_amount := sum_cs4.sum_bank_card_amount;
    sum_cs4.sum_voucher_return_amount := sum_cs4.sum_voucher_amount;
    
    dbms_output.put_line('sum_cs4.sum_all_amount:'||sum_cs4.sum_all_amount);
    dbms_output.put_line('sum_cs4.sum_card_ticket_num:'||sum_cs4.sum_card_ticket_num);
    dbms_output.put_line('sum_cs4.sum_cash_amount:'||sum_cs4.sum_cash_amount);
    dbms_output.put_line('sum_cs4.sum_bank_card_amount:'||sum_cs4.sum_bank_card_amount);
    dbms_output.put_line('sum_cs4.sum_voucher_amount:'||sum_cs4.sum_voucher_amount);
    
  end;
  
begin
  -- 初始化
  null;
end KP_CFCS4;

其中用到的对象(自定义类型):
  KS_CS4SUM:

create or replace type KS_CS4SUM as object
(
  -- Author  : qinxike
  -- Created : 2012-10-24 16:42:48
  -- Purpose : 拆分财收四汇总对象
  
  -- Attributes
  
  --公用属性  
  --约定票据类型【1:电子客票,2:改签原票,3:退票】,有些字段为公用字段,我用类型ID标出。
  sum_ticket_num int,                 --总张数(包括改签原票),未用
  
  --正常售票统计的属性
  sum_sell_ticket_num int,            --发售客票张数(778)(总张数-改签原票张数),【1】
  sum_card_ticket_num int,            --电子支付银行卡张数(980),【1,2,3】
  sum_all_amount int,                 --费用合计(770)=981+982+983,【1,2,3】
  sum_distant_amount int,             --异地票总票价(773),【1,3】
  sum_cash_amount int,                --现金票总票价(981),【1,2,3】
  sum_pet_card_amount int,            --储值卡总票价(982),【1】
  sum_bank_card_amount int,           --银行卡总票价(983),【1,2,3】
  sum_voucher_amount int,             --乘车证总票价(984),【1,2,3】
  sum_cash_resign_amount int,         --现金改签新票总票价(986),【1】
  sum_bank_card_resign_amount int,    --银行卡改签新票总票价(988),【1】
 
  --改签票增加的属性
  sum_cash_return_amount int,         --现金净退款(993),【2,3】
  sum_bank_card_return_amount int,    --银行卡净退款(995),【2,3】
  sum_voucher_return_amount int,     --乘车证净退款(996),【2,3】
  
  --退票增加的属性
  --退票费用总和 = 退票手续费(767) + 挂失补票手续费(769)
  sum_loss_compen_amount int,            --挂失补退票款额(766)【3】
  sum_loss_compen_charge int,            --挂失补票手续费(769)【3】
  sum_return_charge int,                 --退票(手续)费(767)【3】
  sum_ticket_return_amount int,          --客票净退款(768)【3】
  sum_cash_return_charge int,            --现金退票(手续)费(987)【3】
  sum_bank_card_return_charge int,       --银行卡退票(手续)费(989)【3】
  sum_voucher_return_charge int          --乘车证退票(手续)费(990)【3】
  
)

java后台代码部分:
1.主要代码:
说明:使用了spring的jdbcTemplate执行存储过程(hibernate的session.doWork()无返回值,无语),返回自定义对象(ksCs4SumData),这个对象就是对应存储过程定义的自定义类型(KS_CS4SUM)。其中还用到了java.sql.CallableStatement.getObject(int parameterIndex, Map<String, Class<?>> map)方法进行转换和获取对象,具体是用一个Map映射数据库类型名到类对象:typeMap.put("PTCHECK.KS_CS4SUM", KsCs4SumData.class);然后在KsCs4SumData类中实现具体的读取/写入过程(参考KsCs4SumData的代码)

  /**
     * 财收四汇总合计[统计级别有 单张,窗口,车站].调用存储过程实现
     * @param cs4Level 统计标识
     * @param cs4Condition 统计条件VO
     * @return KsCs4SumData 合计对象
     */
   @Override @SuppressWarnings(
"unchecked") @Transactional(timeout=2*60*1000) public KsCs4SumData callCs4Sum(String cs4Level, KsCs4ConditionVo cs4Condition){ String callSQL = ""; String preWhereSQL = "";//预定义的whereSQL,解决final的whereSQL不能重复赋值的问题 String ticketTypeId = cs4Condition.getTicketTypeId(); if(ticketTypeId.equals("1")){ //售票统计,使用主表的whereSQL callSQL = "{call kp_cfcs4.get_Cs4_Sum(?,?)}"; preWhereSQL = KsCs4Action.makeSQLStrWithSup(cs4Level,cs4Condition); }else if(ticketTypeId.equals("2")){ //改签统计,使用主表的whereSQL callSQL = "{call kp_cfcs4.get_cs4_resign_sum(?,?)}"; preWhereSQL = KsCs4Action.makeSQLStrWithSup(cs4Level,cs4Condition); }final String whereSQL = preWhereSQL; final String typeName = "KS_CS4SUM";//Oracle自定义类型的名称 log.info("拆分财收四合计.统计项基础whereSQL:"+whereSQL); //执行存储过程 KsCs4SumData ksCs4SumData = super.ptcheckJdbcTemplate.execute(callSQL, new CallableStatementCallback<KsCs4SumData>() { @Override public KsCs4SumData doInCallableStatement(CallableStatement cstmt) throws SQLException, DataAccessException { // 使用oracle的CallableStatement // OracleCallableStatement ocs = (OracleCallableStatement) cstmt; // ocs.setString(1, whereSQL); // ocs.registerOutParameter(2, OracleTypes.STRUCT, TypeName); // ocs.execute(); // // 获取对象,使用struct // // STRUCT struct = (STRUCT)ocs.getObject(2); // // Object[] objs = struct.getAttributes();//返回对象的所有属性 // // for(Object obj :objs) // // System.out.println(obj);//输出对象第一个值 // 使用java的CallableStatement cstmt.setString(1, whereSQL); cstmt.registerOutParameter(2, Types.STRUCT,typeName); cstmt.execute(); // 获取对象,使用typeMap转换成po对象 Map typeMap = new HashMap(); typeMap.put("PTCHECK.KS_CS4SUM", KsCs4SumData.class); KsCs4SumData cs4SumData = (KsCs4SumData) cstmt.getObject(2, typeMap); log.info("KsCs4SumData:" + cs4SumData); return cs4SumData; } }); return ksCs4SumData; }

2.用到的对象:
  KsCs4SumData:该对象也有限制条件,就是发实现java.sql.SQLData接口,覆盖getSQLTypeName(),readSQL(SQLInput stream, String typeName),writeSQL(SQLOutput stream)方法。其实就是在存储过程返回的类型上一个个属性(attribute)读取/写入KsCs4SumData(通过java.sql.CallableStatement.getObject(int parameterIndex, Map<String, Class<?>> map)执行)。
  另我曾在sql代码中定义该类型而不是单独声明成Type,但后面发现java中无法识别该类型,必须定义到Types下:
                                 

package com.revenue.auditkp.vo;

import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;

/**
 * 对应数据库自定义类型KS_CS4SUM
 * 字段的详细解释见数据库中KS_CS4SUM的说明
 * @author qinxike
 * 
 */
public class KsCs4SumData implements SQLData {

    private String SQLTypeName = "";// 数据库完全限定类型名称PTCHECK.KS_CS4SUM

    private int sumTicketNum; // --总张数(包含改签原票)
    private int sumSellTicketNum;// 发售客票张数(不包含改签原票)
    private int sumCardTicketNum;// 电子支付银行卡张数
    private int sumAllAmount; // 费用合计
    private int sumDistantAmount;// 异地票总票价
    private int sumCashAmount;// 现金票总票价
    private int sumPetCardAmount;// 储值卡总票价
    private int sumBankCardAmount;// 银行卡总票价
    private int sumVoucherAmount;// 乘车证总票价
    private int sumCashResignAmount;// 现金改签新票总票价
    private int sumBankCardResignAmount;// 银行卡改签新票总票价
    //改签原票增加的字段
    private int sumCashReturnAmount;        //--现金净退款(993),【2,3】
    private int sumBankCardReturnAmount;    //--银行卡净退款(995),【2,3】
    private int sumVoucherReturnAmount;     //--乘车证净退款(996),【2,3】
    //退票增加的字段
    private int sumLossCompenAmount;                //--挂失补退票款额(766)【3】
    private int sumLossCompenCharge;                  //--挂失补票手续费(769)【3】
    private int sumReturnCharge;                     //--退票(手续)费(767)【3】
    private int sumTicketReturnAmount;              //--客票净退款(768)【3】
    private int sumCashReturnCharge;                //--现金退票(手续)费(987)【3】
    private int sumBankCardReturnCharge;               //--银行卡退票(手续)费(989)【3】
    private int sumVoucherReturnCharge;             //--乘车证退票(手续)费(990)【3】
    
    @Override
    public String getSQLTypeName() throws SQLException {
        return SQLTypeName;
    }

    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        SQLTypeName = typeName;
        
        sumTicketNum = stream.readInt(); 
        sumSellTicketNum = stream.readInt();
        sumCardTicketNum = stream.readInt();
        sumAllAmount = stream.readInt();
        sumDistantAmount = stream.readInt();
        sumCashAmount = stream.readInt();
        sumPetCardAmount = stream.readInt();
        sumBankCardAmount = stream.readInt();
        sumVoucherAmount = stream.readInt();
        sumCashResignAmount = stream.readInt();
        sumBankCardResignAmount = stream.readInt();
        //改签原票增加的字段
        sumCashReturnAmount=stream.readInt();
        sumBankCardReturnAmount=stream.readInt();
        sumVoucherReturnAmount=stream.readInt();
        //退票增加的字段
        sumLossCompenAmount=stream.readInt();     
        sumLossCompenCharge=stream.readInt();     
        sumReturnCharge=stream.readInt();         
        sumTicketReturnAmount=stream.readInt();   
        sumCashReturnCharge=stream.readInt();     
        sumBankCardReturnCharge=stream.readInt(); 
        sumVoucherReturnCharge=stream.readInt();  
    }

    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeInt(sumTicketNum);
        stream.writeInt(sumSellTicketNum);
        stream.writeInt(sumCardTicketNum);
        stream.writeInt(sumAllAmount);
        stream.writeInt(sumDistantAmount);
        stream.writeInt(sumCashAmount);
        stream.writeInt(sumPetCardAmount);
        stream.writeInt(sumBankCardAmount);
        stream.writeInt(sumVoucherAmount);
        stream.writeInt(sumCashResignAmount);
        stream.writeInt(sumBankCardResignAmount);
        //改签原票增加的字段
        stream.writeInt(sumCashReturnAmount);
        stream.writeInt(sumBankCardReturnAmount);
        stream.writeInt(sumVoucherReturnAmount);
        //退票增加的字段
        stream.writeInt(sumLossCompenAmount);
        stream.writeInt(sumLossCompenCharge);
        stream.writeInt(sumReturnCharge);
        stream.writeInt(sumTicketReturnAmount);
        stream.writeInt(sumCashReturnCharge);
        stream.writeInt(sumBankCardReturnCharge);
        stream.writeInt(sumVoucherReturnCharge);
    }

   //getter setter略
}

至此,我们的程序可以工作了,调用存储过程,返回一个定义好的po对象。感觉是不是很复杂?我感觉是结果挺简单,过程挺复杂。涉及到的东西较多,依赖多,就是不太实用,少一个东西就起不来,还不如用中间表缓存数据进行查询来得方便。我认为除非是有必要的理由,否则就不要用这东西了。但存储过程返回多个值的情况还是有的,有没有更简便的实现方式呢?

原文地址:https://www.cnblogs.com/qinxike/p/2739994.html