在一个项目中,有一张参数表保存了一些全局变量,比如当前会计周期等。
通过一个包(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;