存储过程实现报表功能

声明:这是我两星期的成果,第一次用存储过程做报表,希望大家多提问题,并尊重我的劳动成果

by:zyz    j2EE交流群:28533286

Oracle存储过程实现报表功能

【摘要】:该文介绍了如何使用Java调用Oracle存储过程实现报表功能,并给出了实例。

【关键词】:Java  Oracle  PL/SQL    

 

问题提出

       企业应用软件系统中报表功能几乎是必须的,业务数据都是通过报表、图表等形式展现给客户,这样的好处是能直观、快捷的让用户获得所需信息。通常,报表上展现的数据分散在不同的业务表中,一张报表对应有一条或者多条SQL语句来完成从不同的业务表中提取符合条件的数据,报表的复杂程度直接决定着SQL语句的复杂度,而且用户的报表经常会变动,这将产生了很多的维护工作,目前为止已经遇到的困难有如下这些:

1)用SQL实现报表功能,复杂的报表通常会写到几百行代码,现在我们写过最多的SQL已经有四百多行,用户报表改变时维护该条SQL语句困难、费时。

2)实现报表的SQL语句写在Java应用程序中,每次调用的时候都要发送到数据库服务器,并且重新编译,这给网络造成了压力,同时每次编译对性能也造成影响。

3DAO实现类中编写的SQL语句修改后必须重启应用服务器才能将此次修改装载到内存中使用,重启应用服务必须在空闲时段完成。

4)运销系统报表展现工具使用ATGrid,而ATGrid支持的数据源只有ResultSetXml两种,我们采用的是ResultSet数据源。HibernateDAO层必须将session关闭才能释放数据库连接,这样返回给ATGrid的数据源rs是一个空引用,没有指向任何资源,所以取不到任何数据,只有session不关闭才能取到数据,但是这样数据库连接被挂起,不能释放。

引入存储过程可以解决以上四个问题。

 

存储过程介绍

存储过程是由流控制和SQL语句集书写的过程,这个过程经编译和优化后存储在数据库服务器中,应用程序使用时只要调用即可。它具有以下优点:

1)存储过程的能力大大增强了SQL语言的功能和灵活性。存储过程可以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算,如解决报表问题。

2)在运行存储过程前,数据库已对其进行了语法和句法分析,并给出了优化执行方案。这种已经编译好的过程可极大地改善SQL语句的性能。由于执行SQL语句的大部分工作已经完成,所以存储过程能以极快的速度执行。

  3)可以降低网络的通信量。

        4)使体现企业规则的运算程序放入数据库服务器中,以便集中控制。当企业规则发生变化时在服务器中改变存储过程即可,无须修改任何应用程序。

 

报表存储过程设计

       存储过程实现报表功能主要思想集中在临时表上,即每张用户报表对应一张临时数据表,该临时数据表的字段和报表表头要求的指标一一对应。

Oracle的临时表分为两种,事务级别临时表和Session级别临时表,此设计中使用Session级别临时表,使用Session级别临时表的好处在于其中的数据是Session级别隔离的,不同用户请求的数据相互隔离,互不干扰,每次用户访问报表时,存储过程将符合条件的数据从不同的物理表中抓取到临时数据表中,并且直接从临时数据表中返回数据给用户。

由于运销系统中使用apachedbcp数据库连接池组件,应用服务器(Tomcat)启动时,就已经与Oracle创建了配置文件中指定数量的连接,此连接会持有Session会话,造成临时表中数据不会即时删除,而连接池又是应用服务器(Tomcat)自己管理的,多线程访问时,数据库连接被分配给线程是随机的,所以会出现数据访问错误的情况,引入临时参数表可以解决以上问题。

临时参数表也是Session级别的,它和临时数据表是同时被一个连接访问的,所以它们的Session隔离应该是同步的。临时参数表中存储的是调用存储过程的参数列表,当调用存储过程时,首先将此次调用的参数列表与临时参数表中的参数列表比较,如果临时参数表中没有记录或者比较不相等时则将此次调用的参数列表写入到临时参数表中,同时将符合条件的数据写入临时数据表中,并且从临时数据表中返回符合条件的数据给Java应用程序,下次调用存储过程时,如果参数列表相同,则直接从临时数据表中返回数据给Java应用程序,这样可以起到缓存的作用,提高存储过程效率。

为什么Hibernate Session能关闭

       SQL语句实现报表使用的StatementResultSet为:

class org.apache.commons.dbcp.DelegatingStatement       

class org.apache.commons.dbcp.DelegatingResultSet    

       存储过程实现报表使用的StatementResultSet为:

class org.apache.commons.dbcp.DelegatingCallableStatement

class oracle.jdbc.driver.OracleResultSetImpl

       由于ResultSet接口的实现方式不同,造成ResultSet类型的引用变量rs所指向的资源释放时机不同。

 

性能测试

       按照以上设计方式实现存储过程后,使用JMeter进行了性能测试,测试结果显示,在大并发量的情况下,使用存储过程比使用SQL语句性能更好。测试数据如下表所示:

 

 

Label

# Samples

Average

Median

90% Line

Min

Max

Error %

Throughput

KB/sec

存储过程

3000

230

231

286

36

606

0

42.55681 sec

252.3902

SQL

3000

254

239

268

37

3118

0

38.79979 sec

230.1085

 

二、样例(报表模版、java调用存储过程代码、存储过程代码)
  执行流程图

存储过程执行流程图

Java调用存储过程代码

     String sql="{ call PAK_COLLECT_LOCAL.PRO_MAIN(?,?,?,?,?,?,?) }";

     CallableStatement cs = session.connection().prepareCall(sql);

     cs.setString(1, mdDateStart);

     cs.setString(2, mdDateEnd);

     cs.setString(3, csName);

     cs.setString(4, mName);

     cs.setString(5, ciName);

     cs.setString(6, remark);

     cs.registerOutParameter(7, oracle.jdbc.OracleTypes.CURSOR);

     cs.execute();

     rs = (ResultSet) cs.getObject(7);

     cs.close();

 

 

存储过程代码

create or replace package PAK_COLLECT_LOCAL authid current_user is

  -- Author  : ZYZ
  -- Created : 2009-4-23 11:46:56
  -- Purpose : perchaseMeasureToCollect

  --定义游标类型
  type t_cursor is ref cursor;
  -------创建临时表----
  procedure pro_temptable;
  -----往临时表中插入数据-----
  procedure pro_inserttemptable(p_mdDateStart in varchar2,
                                p_mdDateEnd   in varchar2,
                                p_csName      in varchar2,
                                p_mName       in varchar2,
                                p_ciName      in varchar2,
                                p_remark      in varchar2,
                                o_rs          out t_cursor);
  ------返回结果集-----
  procedure pro_get_rs(o_rs out t_cursor);

  ------Main方法-------
  procedure pro_main(p_mdDateStart in varchar2,
                     p_mdDateEnd   in varchar2,
                    
                     p_csName in varchar2,
                     p_mName  in varchar2,
                     p_ciName in varchar2,
                     p_remark in varchar2,
                     o_rs     out t_cursor);
end PAK_COLLECT_LOCAL;

create or replace package body PAK_COLLECT_LOCAL is
  --创建临时表
  procedure pro_temptable is
    v_counter number;
    v_str_sql varchar2(1000); --根据DDL的长度调整大小
  begin
    --查询临时表是否存在
    select count(table_name)
      into v_counter
      from user_tables u
     where u.table_name = 'T_COLLECT_LOCAL';
    if v_counter < 1 then
      --定义临时表DDL
      v_str_sql := 'create global temporary table T_COLLECT_LOCAL(              
           m_name varchar2(50),
           ci_name varchar2(50),
           ct_name varchar2(50),               
           md_outnum number(10),
           md_outtunnage number(10,2),
           md_intruck number(10),
           md_intunnage number(10,2),
           md_redgreen number(10,2),              
           smd_outnum number(10),
           smd_outtunnage number(10,2),
           smd_intruck number(10),
           smd_intunnage number(10,2),
           smd_redgreen number(10,2)
         )on commit preserve rows';
      execute immediate v_str_sql; --动态SQL创建临时表
    end if;
    --创建参数表
    begin
      select count(table_name)
        into v_counter
        from user_tables u
       where u.table_name = 'T_PARAS';
      if v_counter < 1 then
        v_str_sql := 'create global temporary table T_PARAS(
                 report_name varchar2(50),
                 p_1 varchar2(50),
                 p_2 varchar2(50),
                 p_3 varchar2(50),
                 p_4 varchar2(50),
                 p_5 varchar2(50),
                 p_6 varchar2(50),
                 p_7 varchar2(50),
                 p_8 varchar2(50),
                 p_9 varchar2(50),
                 p_10 varchar2(50),
                 p_11 varchar2(50),
                 p_12 varchar2(50),
                 p_13 varchar2(50),
                 p_14 varchar2(50),
                 p_15 varchar2(50),
                 p_16 varchar2(50),
                 p_17 varchar2(50),
                 p_18 varchar2(50),
                 p_19 varchar2(50),
                 p_20 varchar2(50)
       )on commit preserve rows';
        execute immediate v_str_sql;
      end if;
    end;
  end pro_temptable;
  ------传入参数检测-------
  procedure pro_check_parameters(p_mdDateStart in varchar2,
                                 p_mdDateEnd   in varchar2,
                                 p_csName      in varchar2,
                                 p_mName       in varchar2,
                                 p_ciName      in varchar2,
                                 p_remark      in varchar2,
                                 p_ocounter    out number) is
    v_str_sql     varchar2(500);
    v_report_name varchar2(50) := 'COLLECT_LOCAL';
    v_counter     number;
    v_counter2    number;
 
    v_mdDateStart varchar2(50) := p_mdDateStart;
    v_mdDateEnd   varchar2(50) := p_mdDateEnd;
    v_csName      varchar2(50) := p_csName;
    v_mName       varchar2(50) := p_mName;
    v_ciName      varchar2(50) := p_ciName;
    v_remark      varchar2(50) := p_remark;
  begin
    if v_mdDateStart is null then
      v_mdDateStart := '#';
    end if;
    if v_mdDateEnd is null then
      v_mdDateEnd := '#';
    end if;
    if v_csName is null then
      v_csName := '#';
    end if;
    if v_mName is null then
      v_mName := '#';
    end if;
    if v_ciName is null then
      v_ciName := '#';
    end if;
    if v_remark is null then
      v_remark := '#';
    end if;
 
    v_str_sql := 'select count(tp.report_name) from T_PARAS tp
    where tp.report_name = ''' || v_report_name ||
                 ''' and
    tp.p_1 = ''' || v_mdDateStart || ''' and
    tp.P_2 = ''' || v_mdDateEnd || ''' and
    tp.P_3 = ''' || v_csName || ''' and
    tp.P_4 = ''' || v_mName || ''' and
    tp.P_5 = ''' || v_ciName || ''' and
    tp.P_6 = ''' || v_remark || ''' ';
    execute immediate v_str_sql
      into v_counter;
    v_str_sql := 'select count(tp.report_name) from T_PARAS tp
    where tp.report_name = ''' || v_report_name || '''';
    execute immediate v_str_sql
      into v_counter2;
    if v_counter2 = 0 then
      begin
        v_str_sql := 'insert into T_PARAS(report_name,p_1,p_2,p_3,p_4,p_5,p_6)
           values(''' || v_report_name || ''',''' ||
                     v_mdDateStart || ''',''' || v_mdDateEnd || ''',''' ||
                     v_csName || ''',
           ''' || v_mName || ''',''' || v_ciName ||
                     ''',''' || v_remark || ''')';
        execute immediate v_str_sql;
      end;
    elsif v_counter = 0 and v_counter2 <> 0 then
      begin
        v_str_sql := 'update T_PARAS tp set
                   tp.p_1=''' || v_mdDateStart || ''',
                   tp.p_2=''' || v_mdDateEnd || ''',
                   tp.p_3=''' || v_csName || ''',
                   tp.p_4=''' || v_mName || ''',
                   tp.p_5=''' || v_ciName || ''',
                   tp.p_6=''' || v_remark ||
                     ''' where tp.report_name = ''' || v_report_name || '''';
        execute immediate v_str_sql;
      end;
    else
      NULL;
    end if;
    p_ocounter := v_counter;
  end pro_check_parameters;
  --往临时表中插入数据
  procedure pro_inserttemptable(p_mdDateStart in varchar2,
                                p_mdDateEnd   in varchar2,
                                p_csName      in varchar2,
                                p_mName       in varchar2,
                                p_ciName      in varchar2,
                                p_remark      in varchar2,
                                o_rs          out t_cursor) is
    --定义变量,此处变量个数和类型与临时表一致,用于存储向临时表中插入的数据
    m_name         varchar2(50);
    ci_name        varchar2(50);
    ct_name        varchar2(50);
    md_outnum      number(10);
    md_outtunnage  number(10, 2);
    md_intruck     number(10);
    md_intunnage   number(10, 2);
    md_redgreen    number(10, 2);
    smd_outnum     number(10);
    smd_outtunnage number(10, 2);
    smd_intruck    number(10);
    smd_intunnage  number(10, 2);
    smd_redgreen   number(10, 2);
 
    v_str_sql_thismonth varchar2(1500);
    v_str_sql_total     varchar2(1500);
    v_str_sql           varchar2(4000);
    v_str_sql_public    varchar2(1000);
  begin
    --公共部分sql
    v_str_sql_public := 'select m_name,ci_name,ct_name,sum(md_outtruck) as md_outnum,sum(md_outtunnage) as md_outtunnage,
                         sum(md_intruck) as md_intruck,sum(md_intunnage) as md_intunnage,
                         sum(md_redgreen) as md_redgreen
                  from localmeasuredaily
                       left join manufacturer on localmeasuredaily.m_id = manufacturer.m_id
                       left join coalinfo on localmeasuredaily.ci_id = coalinfo.ci_id
                       left join CHECKSTATION on localmeasuredaily.cs_id = CHECKSTATION.cs_id
                       left join coaltype on localmeasuredaily.ct_id = coaltype.ct_id
                  where CHECKSTATION.cs_name like ''%' ||
                        p_csName || '%''
                        and manufacturer.m_name like ''%' ||
                        p_mName || '%''
                        and coalinfo.ci_name like ''%' ||
                        p_ciName || '%''
                        and localmeasuredaily.md_remark like ''%' ||
                        p_remark || '%''';
    --本月
    v_str_sql_thismonth := '' || v_str_sql_public || '
                  and to_char(md_date,''yyyy-MM'') like ''%' ||
                           p_mdDateEnd || '%''
                  group by m_name,ci_name,ct_name';
    --累计
    v_str_sql_total := '' || v_str_sql_public || '
                  and md_date between to_date(''' ||
                       p_mdDateStart ||
                       ''',''yyyy-MM-dd'') and add_months(to_date(''' ||
                       p_mdDateEnd ||
                       ''',''yyyy-MM''),1)
                  group by m_name,ci_name,ct_name';
    --汇总
    v_str_sql := 'select singleTs.m_name,singleTs.ci_name,singleTs.ct_name,
                      singleTs.md_outnum, singleTs.md_outtunnage,singleTs.md_intruck,
                      singleTs.md_intunnage,singleTs.md_redgreen, sumTs.md_outnum,
                      sumTs.md_outtunnage,sumTs.md_intruck,sumTs.md_intunnage,sumTs.md_redgreen
                 from (' || v_str_sql_thismonth ||
                 ') singleTs,(' || v_str_sql_total ||
                 ') sumTs
                 where sumTs.ci_name = singleTs.ci_name and sumTs.m_name = singleTs.m_name
                 order by m_name,ct_name,ci_name';
    begin
      open o_rs for v_str_sql;
      execute immediate 'truncate table T_COLLECT_LOCAL'; --动态SQL清除临时表中的数据  
      loop
        --移动游标将结果集中的值赋给变量
        fetch o_rs
          into m_name, ci_name, ct_name, md_outnum, md_outtunnage, md_intruck, md_intunnage, md_redgreen, smd_outnum, smd_outtunnage, smd_intruck, smd_intunnage, smd_redgreen;
        if o_rs %notfound then
          exit;
        end if;
     
        --动态SQL将数据插入临时表      
        execute immediate 'insert into T_COLLECT_LOCAL values(''' || m_name ||
                          ''',''' || ci_name || ''',''' || ct_name || ''',
                                                          ''' ||
                          md_outnum || ''',''' || md_outtunnage || ''',''' ||
                          md_intruck || ''',
                                                          ''' ||
                          md_intunnage || ''',''' || md_redgreen || ''',''' ||
                          smd_outnum || ''',
                                                          ''' ||
                          smd_outtunnage || ''',''' || smd_intruck ||
                          ''',''' || smd_intunnage || ''',
                                                          ''' ||
                          smd_redgreen || ''')';
      end loop;
    end;
 
  end pro_inserttemptable;

  --返回结果集
  procedure pro_get_rs(o_rs out t_cursor) is
    sql_str varchar2(500);
  begin
    sql_str := 'select * from T_COLLECT_LOCAL'; --从临时表中检索数据
    open o_rs for sql_str; --打开游标 
  end pro_get_rs;

  --Main方法
  procedure pro_main(p_mdDateStart in varchar2,
                     p_mdDateEnd   in varchar2,
                     p_csName      in varchar2,
                     p_mName       in varchar2,
                     p_ciName      in varchar2,
                     p_remark      in varchar2,
                     o_rs          out t_cursor) is
    v_ocounter number;
    ocounter   number;
  begin
    --创建临时表
    pro_temptable;
    ------传入参数检测-------
    pro_check_parameters(p_mdDateStart,
                         p_mdDateEnd,
                         p_csName,
                         p_mName,
                         p_ciName,
                         p_remark,
                         v_ocounter);
    ocounter := v_ocounter;
    if ocounter < 1 then
      --往临时表中插入数据
      pro_inserttemptable(p_mdDateStart,
                          p_mdDateEnd,
                          p_csName,
                          p_mName,
                          p_ciName,
                          p_remark,
                          o_rs);
    end if;
    --返回结果集
    pro_get_rs(o_rs);
  end pro_main;

end PAK_COLLECT_LOCAL;

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值