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

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

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

一、日志记录表

日志记录表用于配置:

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

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

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

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

下面是个例子:

二、日志表

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

三、触发器

触发器是方案的核心。

触发器里可以使用: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 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
92   --execute immediate v$tri_sql;
93   insert into tmp_3( script ) values(v$tri_sql) ;
94 end ;

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

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

1 begin
2   -- Call the procedure
3   proc_create_log_trigger(‘phd_pe_sjxmlr‘);
4 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_sjxmlr‘,
132          ‘h_qjbh‘,
133          :old.h_qjbh,
134          :new.h_qjbh,
135          :old.col_id,
136          :old.h_qjbh,
137          ‘h_khdxbh:‘ || :old.h_khdxbh || ‘;‘,
138          v$opuser,
139          sysdate);
140     end if;
141     if updating(‘h_khdxbh‘) and
142        (:new.h_khdxbh is not null or :old.h_khdxbh is not null) and
143        :new.h_khdxbh <> :old.h_khdxbh then
144       insert into phs_data_upd_log
145         (op_code,
146          id,
147          data_id,
148          table_name,
149          col,
150          old_value,
151          new_value,
152          kv1,
153          kv2,
154          oth_kv,
155          op_user,
156          op_time)
157       values
158         (‘update‘,
159          seq_data_log.nextval,
160          :old.h_guid,
161          ‘phd_pe_sjxmlr‘,
162          ‘h_khdxbh‘,
163          :old.h_khdxbh,
164          :new.h_khdxbh,
165          :old.col_id,
166          :old.h_qjbh,
167          ‘h_khdxbh:‘ || :old.h_khdxbh || ‘;‘,
168          v$opuser,
169          sysdate);
170     end if;
171   end if;
172 end;

2、在触发器中得到mis系统的账号

在日志中,需要记录操作用户,这个用户是业务系统的用户而不是数库的账号。在触发器中如何能得到应用系统的用户呢。

可以参见另一篇文章:利用oracle context 向 oracle 传值 https://www.cnblogs.com/senline/p/10345006.html

上面的触发器使用如下代码得到业务系统的用户账号:

1 select sys_context(‘hcm_context‘, ‘hcm_user‘) into v$opuser from dual;

前提是,业务系统在执行修改代码时,要先执行以下语句,将账号写入到数据库session中:

1 proc_hcm_context( ‘hcm_context‘, ‘hcm_user‘, ‘9999‘);
proc_hcm_context 是个存储过程。

四、日志的例子我们执行以下sql语句,然后查看日志记录的数据:
1 update phd_pe_sjxmlr set h_value=0.000001 where h_guid = ‘37E10088B5C0459C10EAF12F34962D17‘;

查看日志:

1 SELECT * FROM phs_data_upd_log ;

五、总结

以上给出了一个通过触发器记录数据修改日志的一种通用解决方案。

核心是,可配置,并且根据配置信息,自动创建触发器,通过触发器记录修改日志。业务系统的账号通过oracle session context 从业务系统传到oracle服务器,通过触发器拿到它。

这种方案有点是对原有系统改动比较小,很容易扩展,使用面很广。

原文地址:https://www.cnblogs.com/senline/p/configurable_datalog_by_trigger.html

时间: 2024-11-04 05:34:42

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

Linux C编程学习4---多文件项目管理、Makefile、一个通用的Makefile

GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相应的更新.如果通过手动去完成这个工作的话,对于小型的项目可能还行,但是对于比较大型的项目就几乎是不可能的. 因此Linux 系统提供了一个自动维护和生成目标程序的工具 make,它可以根据各个模块的更改情况去重新编译连接目标代码 Make 工具的作用就是实现编译连接过程的自动化.它定义了一种语言,用

一个通用的Makefile (转)

据http://bbs.chinaunix.net/thread-2300778-1-1.html的讨论,发现还是有很多人在问通用Makefile的问题,这里做一个总结.也作为以后的参考. 笔者在写程序的时候会遇到这样的烦恼:一个项目中可能会有很多个应用程序,而新建一个应用程序则所有的Makefile都要重写一遍,虽然可以部分的粘帖复制,但还是感觉应该找到更好的解决途径:另外当一个应用程序中包含多个文件夹时通常要在每个目录下创建一个Makefile,当有数十个文件夹时,要创建如此多的Makefi

一个通用的c/c++Makefile模版

一个codeproject上发现的通用c/c++的Makefile模版,比较简单好用,共享之 ############################################################################# # # Generic Makefile for C/C++ Program # # License: GPL (General Public License) # Author: whyglinux <whyglinux AT gmail DOT

移动应用开发(IOS/android等)中一个通用的图片缓存方案讲解(附流程图)

在移动应用开发中,我们经常会遇到从网络请求图片到设备上展示的场景. 如果每次都重复发起请求,浪费流量.浪费电量,用户体验也不佳: 将图片持久化到磁盘也不失为一种策略:但每次从文件读取图片也存在一定的io开销,就算采用此策略,我们也需要控制磁盘缓存的容量,以免占用过多系统资源. 其实没有一个方案可以说是完美的方案,只有最适合自己业务需求的方案,才可以说是一个好方案. 我们下面所讲解的方案具备很强的通用性,设计思路简单而清晰: 1.假设每个网络图片的url具有唯一性,如果网络上的图片变化了,会引起输

编写一个通用的Makefile文件

1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe 1.2体验在VC下程序的编译 a.先编译,在链接 b.修改了哪个文件,就单独编译此文件,在链接 c.修改了哪个头文件,就单独编译使用该头文件的源文件,在链接 1.3在linux下实现上述要求 2.编写一个测试的Makefile 2.1直接编译链接 1 gcc -o test a.c b.c 缺点:改变其中一

一个通用的JavaScript分页

1.JavaScript代码 Java代码   Pagination=function(id) { var totalNum=0; var maxNum=10; var pageUrl=""; var breakpage = 5; var currentposition = 0; var breakspace = 2; var maxspace = 4; var currentpage=1; var perpage=10; var id =id; this.initPage = fun

为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式

为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式,代码如下: public class DataComparer<T>:IEqualityComparer<T> where T:class { private Func<T, T, bool> _compareFunc; public DataComparer(Func<T,T,bool> compareFunc) { this._compareFunc = compare

分享一个通用的分页SQL

又很久没写博客,今天记录一个SQLserver通用分页存储过程(适用于SqlServer2000及以上版本) 1.支持连表 2.支持条件查询 USE [MYDB] GO /****** Object:  StoredProcedure [dbo].[SP_CommonPage] SET QUOTED_IDENTIFIER ON GO ------------------------------------ --用途:分页存储过程(对有主键的表效率极高) --说明: ---------------

(转载)一个通用并发对象池的实现

原文链接,译文链接,原文作者: Sarma Swaranga,本文最早发表于deepinmind,校对:郑旭东 这篇文章里我们主要讨论下如何在Java里实现一个对象池.最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来提高性能了.根本的原因是,创建一个新的对象的开销已经不像过去那样昂贵了. 然而,还是有些对象,它们的创建开销是非常大的,比如线程,数据库连接等这些非轻量级的对象.在任何一个应用程序里面,我们肯定会用到不止一个这样的对象.如果有一