PACKAGE静态缓存数据带来的逻辑隐患

在一个项目中,有一张参数表保存了一些全局变量,比如当前会计周期等。
通过一个包(PACKAGE)来实现业务逻辑,在PACKAGE的初始化程序里,读取相关的全局参数,用于后续处理程序。
但是在调试过程中,发现对参数进行修改后,包的全局变量跟实际库里的值不一致,导致业务逻辑错误。这里重现一下当时的场景:

--参数表结构
CREATE TABLE T_PARA
(PARA_CODE  VARCHAR2(20)
,PARA_NAME  VARCHAR2(60)
,PARA_VALUE VARCHAR2(60)
);
--其中一个参数:当前会计期,值是201801
INSERT INTO T_PARA VALUES('ACCYEARMON','当前会计期','201801');
commit;
/*
--包头
--定义了当前会计期的全局变量
*/
create or replace package PKG_SESSIONDAT is
  --全局变量,当前会计期
  G_ACCYEARMON   VARCHAR2(6);
  --返回G_ACCYEARMON值
  function GET_ACCYEARMON return VARCHAR2;
  --设置参数值
  PROCEDURE SET_ACCYEARMON(V_ACCYEARMON VARCHAR2);

end PKG_SESSIONDAT;

--包体
create or replace package body PKG_SESSIONDAT is
  --函数,返回当前会计期
  function GET_ACCYEARMON return VARCHAR2 IS
    v_ret t_para.para_value%type;
  BEGIN
    v_ret:=G_ACCYEARMON;
    return v_ret;
  END GET_ACCYEARMON;
  --存储过程,设置当前会计期
  PROCEDURE SET_ACCYEARMON(V_ACCYEARMON VARCHAR2) IS
  BEGIN
    UPDATE T_PARA SET PARA_VALUE=V_ACCYEARMON WHERE PARA_CODE='ACCYEARMON';
    COMMIT;
  END SET_ACCYEARMON;

begin
  --包初始化部分,调用包程序就会执行
  SELECT PARA_VALUE INTO G_ACCYEARMON
    FROM T_PARA
    WHERE PARA_CODE='ACCYEARMON';
end PKG_SESSIONDAT;

接下来做个测试,先取出初始会计期的值,然后重新设置该值,再取出来。

SQL> declare
  2    v_true varchar2(6);
  3  begin
  4    dbms_output.put_line('G_ACCYEARMON初始值='||PKG_SESSIONDAT.G_ACCYEARMON);
  5    dbms_output.put_line('GET_ACCYEARMON返回='||PKG_SESSIONDAT.GET_ACCYEARMON);
  6    PKG_SESSIONDAT.SET_ACCYEARMON('201802');
  7    dbms_output.put_line('重置后G_ACCYEARMON='||PKG_SESSIONDAT.G_ACCYEARMON);
  8    dbms_output.put_line('重置后GET_ACCYEARMON返回='||PKG_SESSIONDAT.GET_ACCYEARMON);
  9    select PARA_VALUE into v_true
 10      from T_PARA WHERE PARA_CODE='ACCYEARMON';
 11    dbms_output.put_line('数据库内真实值='||v_true);
 12  end p_test;
 13  /
G_ACCYEARMON初始值=201801
GET_ACCYEARMON返回=201801
重置后G_ACCYEARMON=201801
重置后GET_ACCYEARMON返回=201801
数据库内真实值=201802
PL/SQL procedure successfully completed

可以发现,用包里的存储过程对数据库值进行重置后,再调用包函数,全局变量的值没有变化,尽管数据库的值已经被重写了。
合理的解释是包的初始化程序没有再次执行。
查阅资料后验证了这一点:

在同一个会话中,包的初始化只执行一次。

刚才的示例中,换一个session,得到的值是正确的:

SQL> declare
  2    v_true varchar2(6);
  3  begin
  4    select PARA_VALUE into v_true
  5      from T_PARA WHERE PARA_CODE='ACCYEARMON';
  6    dbms_output.put_line('数据库内真实值='||v_true);
  7    dbms_output.put_line('另一个会话G_ACCYEARMON值='||PKG_SESSIONDAT.G_ACCYEARMON);
  8    dbms_output.put_line('另一个会话GET_ACCYEARMON返回='||PKG_SESSIONDAT.GET_ACCYEARMON);
  9  end;
 10  /
数据库内真实值=201802
另一个会话G_ACCYEARMON值=201802
另一个会话GET_ACCYEARMON返回=201802
PL/SQL procedure successfully completed

Oracle这样做的目的是提高效率、节省资源。但一不留心会给开发人员带来副作用,如何避免这类情况的产生,建议是:
将初始化程序单独封装成存储过程,在适当的时候进行调用。
比如上述案例:

  PROCEDURE Initialization IS
  BEGIN
    SELECT PARA_VALUE INTO G_ACCYEARMON
      FROM T_PARA
      WHERE PARA_CODE='ACCYEARMON';
  END Initialization;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值