一个通用的通过触发器实现的,可配置的表修改日志解决方案

Posted senline

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个通用的通过触发器实现的,可配置的表修改日志解决方案相关的知识,希望对你有一定的参考价值。

在MIS系统中,系统审计功能是很重要的一部分,审计的一部分就是记录数据修改日志。记录数据修改日志有很多种实现方案,有通过后台程序实现的,在修改程序中增加日志代码,也有通过数据库实现的,使用触发器来记录修改日志。本方案采用第二种方案。这种方案的优点是无论你通过什么方式修改数据,都会记录下来,极少需要修改代码;缺点是需要应用程序配合,以便能知道是哪个应用系统的账号做的修改。

本方案的核心是,通过一个配置表,配置哪些表需要记录日志、修改哪些列的时候记录日志。然后根据配置信息,为每个表生成用于记录日志的触发器。

一、日志配置表

  日志配置表用于配置:

1、需要记录日志的表(table_name)

2、修改哪些列时,需要记录日志(log_cols),列名用逗号分隔。

3、该表的id列(id_col)。用于在日志中唯一标识一行数据。

4、记录日志时需要额外记录的一些关键列,列名逗号分隔(key_cols)。

下面是个例子:

 

  二、日志表

 日志表用来记录对数据的修改。

本方案中,使用数据库序列生成日志表的id。使用前,要先创建一个序列SEQ_DATA_LOG:

1 -- Create sequence 
2 create sequence SEQ_DATA_LOG
3 minvalue 1
4 maxvalue 9999999999999999999999999999
5 start with 31
6 increment by 1
7 cache 10;

 

三、触发器

触发器是方案的核心。

触发器里可以使用:new.列名和:old.列名来得到修改的列的旧值和新值。但是列名不能用变量,换句话说,:new不能用在动态sql中,因此就不能读取配置表,来动态取每个需要记录日志的列的旧值和新值。一句话说,你不能使用如下的语句:

vsql := \'select :new.name from dual\';
excute immediate vssql into vName ;

只能在触发器中这样写

select :new.name into vName from dual;

在写触发器的时候就要知道要记录哪些列的日志,要写死了。

没有办法,职能采取一种折中的方式:通过配置信息,自动创建触发器。

1、创建触发器的存储过程

 1 create or replace procedure proc_create_log_trigger(
 2   p$table_name in varchar2, p$tri_script out varchar2
 3 )
 4 is
 5   v$id_col varchar2(30);
 6   v$logid_exp varchar2(100);
 7   v$log_cols varchar2(1000);
 8   v$key_cols varchar2(1000);
 9   v$tri_sql varchar2(4000);
10   v$idcol_exp_ins nvarchar2(1000);
11   v$idcol_exp nvarchar2(1000);
12   v$kv1_exp nvarchar2(1000);
13   v$kv2_exp nvarchar2(1000);
14   v$oth_kv_exp nvarchar2(1000);
15   v$kv1_exp_ins nvarchar2(1000);
16   v$kv2_exp_ins nvarchar2(1000);
17   v$oth_kv_exp_ins nvarchar2(1000);
18   v$opuser_exp nvarchar2(1000);
19   v$count int ;
20 begin
21   v$id_col := null ;
22   select cfg.id_col, cfg.log_cols, cfg.key_cols into v$id_col,v$log_cols,v$key_cols
23     from phs_data_log_cfg cfg
24     where cfg.table_name = p$table_name;
25   if v$id_col is null then
26     return ;
27   end if ;
28 
29   v$tri_sql := \'create or replace trigger tr_\' ||p$table_name || \'_log \' ||
30                \'before delete or insert or update \'||
31                \'on \' || p$table_name || \' \'||
32                \'for each row \'||
33                \'declare \'||
34                \'v$opuser varchar2(100);\'||
35                \'begin \';
36 
37   v$logid_exp :=\'seq_data_log.nextval\';
38   v$idcol_exp_ins := \':new.\'||v$id_col;
39   v$idcol_exp := \':old.\'||v$id_col;
40   v$count := 1;
41   v$kv1_exp := \' \';
42   v$kv2_exp := \' \';
43   v$oth_kv_exp :=\' \';
44   v$kv1_exp_ins := \' \';
45   v$kv2_exp_ins := \' \';
46   v$oth_kv_exp_ins :=\' \';
47   v$opuser_exp := \'v$opuser\';
48   v$tri_sql := v$tri_sql || \'select sys_context(\'\'hcm_context\'\', \'\'hcm_user\'\') into \'||v$opuser_exp||\' from dual;\';
49   for r in(SELECT REGEXP_SUBSTR (replace(v$key_cols,\' \',\'\'), \'[^,]+\', 1,rownum) as v from dual connect by rownum<=LENGTH(v$key_cols) - LENGTH(regexp_replace(v$key_cols, \',\', \'\'))+1) loop
50     if v$count = 1 then 
51       v$kv1_exp := \':old.\'||r.v ; 
52       v$kv1_exp_ins := \':new.\'||r.v ; 
53     end if ;
54     if v$count = 2 then 
55       v$kv2_exp := \':old.\'||r.v ; 
56       v$kv2_exp_ins := \':new.\'||r.v ;
57     end if ;
58     if v$count > 2 then 
59       v$oth_kv_exp := v$oth_kv_exp ||\'\'\'\'||r.v||\':\'\'||:old.\'||r.v||\'||\'\';\'\'\'; 
60       v$oth_kv_exp_ins := v$oth_kv_exp_ins ||\'\'\'\'||r.v||\':\'\'||:new.\'||r.v||\'||\'\';\'\'\';
61     end if ;
62     v$count := v$count + 1;
63   end loop;
64 
65   if v$kv1_exp = \' \' then v$kv1_exp := \'NULL\'; end if;
66   if v$kv2_exp = \' \' then v$kv2_exp := \'NULL\'; end if;
67   if v$oth_kv_exp = \' \' then v$oth_kv_exp := \'NULL\'; end if;
68   if v$kv1_exp_ins = \' \' then v$kv1_exp_ins := \'NULL\'; end if;
69   if v$kv2_exp_ins = \' \' then v$kv2_exp_ins := \'NULL\'; end if;
70   if v$oth_kv_exp_ins = \' \' then v$oth_kv_exp_ins := \'NULL\'; end if;
71 
72   v$tri_sql := v$tri_sql ||
73   \'if inserting then \'||
74     \'insert into phs_data_upd_log( op_code,id,  data_id, table_name, kv1, kv2, oth_kv,op_user, op_time )\'||
75      \' values( \'\'insert\'\',\'||v$logid_exp||\',\'||v$idcol_exp_ins||\',\'\'\'||p$table_name||\'\'\',\'||v$kv1_exp_ins||\',\'||v$kv2_exp_ins ||\',\'||v$oth_kv_exp_ins||\',\'||v$opuser_exp||\', sysdate);\'||
76   \'elsif deleting then \'||
77     \'insert into phs_data_upd_log( op_code, id, data_id, table_name, kv1, kv2, oth_kv,op_user, op_time )\'||
78      \' values( \'\'delete\'\',\'||v$logid_exp||\',\'||v$idcol_exp||\',\'\'\'||p$table_name||\'\'\',\'||v$kv1_exp||\',\'||v$kv2_exp ||\',\'||v$oth_kv_exp||\',\'||v$opuser_exp||\', sysdate);\';
79 
80   v$tri_sql := v$tri_sql ||\'else \';
81   for r in(SELECT REGEXP_SUBSTR (replace(v$log_cols,\' \',\'\'), \'[^,]+\', 1,rownum) as v from dual connect by rownum<=LENGTH(v$log_cols) - LENGTH(regexp_replace(v$log_cols, \',\', \'\'))+1) loop
82     v$tri_sql := v$tri_sql ||
83     \'if updating(\'\'\'||r.v||\'\'\') and (:new.\'||r.v||\' is not null or :old.\'||r.v||\' is not null) and :new.\'||r.v||\'<>:old.\'||r.v||\' then \'||
84     \'insert into phs_data_upd_log( op_code, id, data_id, table_name, col, old_value, new_value, kv1, kv2, oth_kv,op_user, op_time )\'||
85     \' values( \'\'update\'\',\'||v$logid_exp||\',\'||v$idcol_exp||\',\'\'\'||p$table_name||\'\'\',\'\'\'||r.v||\'\'\',:old.\'||r.v||\',:new.\'||r.v||\',\'||v$kv1_exp||\',\'||v$kv2_exp ||\',\'||v$oth_kv_exp||\',\'||v$opuser_exp||\', sysdate);\'||
86     \'end if;\';
87   end loop;
88   
89   v$tri_sql := v$tri_sql || \'end if;\';
90   v$tri_sql := v$tri_sql || \'end;\';
91   p$tri_script := v$tri_sql;
92 end ;

这个存储过程的核心就是,读取某个表的日志配置数据,形成创建用于记录日志的触发器的脚本。

执行以下语句,就可以创建触发器:

declare
   tri_script varchar2(4000);
begin
   -- Call the procedure
   proc_create_log_trigger( p$table_name =>\'phd_pe_sjxmlr\', p$trig_script =>tri_script );
   execute immediate tri_script ;
end;

下面是自动创建的一个触发器的脚本:

  1 create or replace trigger tr_phd_pe_sjxmlr_log
  2   before delete or insert or update on phd_pe_sjxmlr
  3   for each row
  4 declare
  5   v$opuser varchar2(100);
  6 begin
  7   select sys_context(\'hcm_context\', \'hcm_user\') into v$opuser from dual;
  8   if inserting then
  9     insert into phs_data_upd_log
 10       (op_code,
 11        id,
 12        data_id,
 13        table_name,
 14        kv1,
 15        kv2,
 16        oth_kv,
 17        op_user,
 18        op_time)
 19     values
 20       (\'insert\',
 21        seq_data_log.nextval,
 22        :new.h_guid,
 23        \'phd_pe_sjxmlr\',
 24        :new.col_id,
 25        :new.h_qjbh,
 26        \'h_khdxbh:\' || :new.h_khdxbh || \';\',
 27        v$opuser,
 28        sysdate);
 29   elsif deleting then
 30     insert into phs_data_upd_log
 31       (op_code,
 32        id,
 33        data_id,
 34        table_name,
 35        kv1,
 36        kv2,
 37        oth_kv,
 38        op_user,
 39        op_time)
 40     values
 41       (\'delete\',
 42        seq_data_log.nextval,
 43        :old.h_guid,
 44        \'phd_pe_sjxmlr\',
 45        :old.col_id,
 46        :old.h_qjbh,
 47        \'h_khdxbh:\' || :old.h_khdxbh || \';\',
 48        v$opuser,
 49        sysdate);
 50   else
 51     if updating(\'col_id\') and
 52        (:new.col_id is not null or :old.col_id is not null) and
 53        :new.col_id <> :old.col_id then
 54       insert into phs_data_upd_log
 55         (op_code,
 56          id,
 57          data_id,
 58          table_name,
 59          col,
 60          old_value,
 61          new_value,
 62          kv1,
 63          kv2,
 64          oth_kv,
 65          op_user,
 66          op_time)
 67       values
 68         (\'update\',
 69          seq_data_log.nextval,
 70          :old.h_guid,
 71          \'phd_pe_sjxmlr\',
 72          \'col_id\',
 73          :old.col_id,
 74          :new.col_id,
 75          :old.col_id,
 76          :old.h_qjbh,
 77          \'h_khdxbh:\' || :old.h_khdxbh || \';\',
 78          v$opuser,
 79          sysdate);
 80     end if;
 81     if updating(\'h_value\') and
 82        (:new.h_value is not null or :old.h_value is not null) and
 83        :new.h_value <> :old.h_value then
 84       insert into phs_data_upd_log
 85         (op_code,
 86          id,
 87          data_id,
 88          table_name,
 89          col,
 90          old_value,
 91          new_value,
 92          kv1,
 93          kv2,
 94          oth_kv,
 95          op_user,
 96          op_time)
 97       values
 98         (\'update\',
 99          seq_data_log.nextval,
100          :old.h_guid,
101          \'phd_pe_sjxmlr\',
102          \'h_value\',
103          :old.h_value,
104          :new.h_value,
105          :old.col_id,
106          :old.h_qjbh,
107          \'h_khdxbh:\' || :old.h_khdxbh || \';\',
108          v$opuser,
109          sysdate);
110     end if;
111     if updating(\'h_qjbh\') and
112        (:new.h_qjbh is not null or :old.h_qjbh is not null) and
113        :new.h_qjbh <> :old.h_qjbh then
114       insert into phs_data_upd_log
115         (op_code,
116          id,
117          data_id,
118          table_name,
119          col,
120          old_value,
121          new_value,
122          kv1,
123          kv2,
124          oth_kv,
125          op_user,
126          op_time)
127       values
128         (\'update\',
129          seq_data_log.nextval,
130          :old.h_guid,
131          \'phd_pe_sjxmlr90行代码,搞定日志监控框架

java能够实现对数据库的监听吗

使用Spring AOP实现业务依赖解耦

oracle 查询每天写入的数据

登录到 SQL Server 触发器中的表

mysql 日志管理