审核数据库中多个表的历史记录

时间:2014-01-17 07:41:29

标签: java oracle hibernate triggers audit

我的数据库中有3-4个表,我想跟踪它们的变化。

我主要关注更新。

每当发生更新时,我想在审计表中存储先前的条目(值或完整的行)。

我想到的基本栏目如下:

AuditId, TableName, PK1, PK2, PK3, PKVal1, PKVal2, PKVal3,  UpdateType, PrevEntryJSON

JSON格式为:Key:Value我更喜欢随着列不断更改而继续使用它,即使它们没有更改,我也希望保留所有值。

其他选项是删除带有100个列的JSON,这些列的名称与不同的列相同(所有表的累计)。

我想听听别人对此的看法。我怎么能改进它以及我可以面对哪些问题?

通过触发器可能不是更好的方式,但我愿意接受它。

谢谢,

1 个答案:

答案 0 :(得分:1)

我已经看到了一个非常有效的实现,如下所示:

TABLE audit_entry (
    audit_entry_id          INTEGER         PRIMARY KEY,
    audit_entry_type        VARCHAR2(10)    NOT NULL,
    -- ^^ stores 'INSERT' / 'UPDATE' -- / 'DELETE'

    table_name              VARCHAR2(30)    NOT NULL,
    -- ^^ stores the name of the table that is changed

    column_name             VARCHAR2(30)    NOT NULL,
    -- ^^ stores the name of the column that is changed

    primary_key_id          INTEGER         NOT NULL,
    -- ^^ Primary key ID to identify the row that is changed

    -- Below are the actual values that are changed.
    -- If the changed column is a foreign key ID then
    -- below columns tell you which is new and which is old
    old_id                  INTEGER,
    new_id                  INTEGER,

    -- If the changed column is of any other numeric type,
    -- store the old and new values here.
    -- Modify the precision and scale of NUMBER as per your 
    -- choice.
    old_number              NUMBER(18,2),
    new_number              NUMBER(18,2),

    -- If the changed column is of date type, with or without
    -- time information, store it here.
    old_ts                  TIMESTAMP,
    new_ts                  TIMESTAMP,

    -- If the changed column is of VARCHAR2 type,
    -- store it here.
    old_varchar             VARCHAR2(2000),
    new_varchar             VARCHAR2(2000),
    ...
    ... -- Any other columns to store data of other types,
    ... -- e.g., blob, xmldata, etc.
    ...
)

我们创建一个简单的序列,为audit_entry_id提供新的增量整数值:

CREATE SEQUENCE audit_entry_id_seq;

audit_entry这样的表的美妙之处在于,您可以在同一个地方存储有关所有类型的DML的信息 - INSERTUPDATEDELETE

例如,对于插入,请将old_*列保留为空,并使用您的值填充new_*

对于更新,只要更改了old_*new_*列,就会填充这些列。

对于删除,只需填充old_*列并保持new_*为空。

当然,为audit_entry_type输入适当的值。 ; 0)

然后,例如,您有一个如下表:

TABLE emp (
    empno           INTEGER,
    ename           VARCHAR2(100) NOT NULL,
    date_of_birth   DATE,
    salary          NUMBER(18,2) NOT NULL,
    deptno          INTEGER -- FOREIGN KEY to, say, department
    ...
    ... -- Any other columns that you may fancy.
    ...
)

只需在此表上创建一个触发器,如下所示:

CREATE OR REPLACE TRIGGER emp_rbiud
-- rbiud means Row level, Before Insert, Update, Delete
BEFORE INSERT OR UPDATE OR DELETE
ON emp
REFERENCING NEW AS NEW OLD AS OLD
DECLARE
    -- any variable declarations that deem fit.
BEGIN
    WHEN INSERTING THEN
        -- Of course, you will insert empno.
        -- Let's populate other columns.

        -- As emp.ename is a not null column, 
        -- let's insert the audit entry value directly.
        INSERT INTO audit_entry(audit_entry_id,
                                audit_entry_type,
                                table_name,
                                column_name,
                                primary_key,
                                new_varchar)
        VALUES(audit_entry_id_seq.nextval,
               'INSERT',
               'EMP',
               'ENAME',
               :new.empno,
               :new.ename);

        -- Now, as date_of_birth may contain null, we do:
        IF :new.date_of_birth IS NOT NULL THEN
            INSERT INTO audit_entry(audit_entry_id,
                                    audit_entry_type,
                                    table_name,
                                    column_name,
                                    primary_key,
                                    new_ts)
            VALUES(audit_entry_id_seq.nextval,
                   'INSERT',
                   'EMP',
                   'DATE_OF_BIRTH',
                   :new.empno,
                   :new.date_of_birth);
        END IF;

        -- Similarly, code DML statements for auditing other values
        -- as per your requirements.

    WHEN UPDATING THEN
        -- This is a tricky one.
        -- You must check which columns have been updated before you
        -- hurry into auditing their information.

        IF :old.ename != :new.ename THEN
            INSERT INTO audit_entry(audit_entry_id,
                                    audit_entry_type,
                                    table_name,
                                    column_name,
                                    primary_key,
                                    old_varchar,
                                    new_varchar)
            VALUES(audit_entry_id_seq.nextval,
                   'INSERT',
                   'EMP',
                   'ENAME',
                   :new.empno,
                   :old.ename,
                   :new.ename);
        END IF;

        -- Code further DML statements in similar fashion for other
        -- columns as per your requirement.

    WHEN DELETING THEN
        -- By now you must have got the idea about how to go about this.
        -- ;0)
END;
/

只需提醒一句:选择要选择审核的表和列,因为无论如何,这个表会有大量的行。此表上的SELECT语句将比您预期的要慢。

我真的很想看到任何其他类型的实现,因为这将是一个很好的学习经验。希望你的问题得到更多的答案,因为这是我见过的审计表的最佳实现,我仍然在寻找使其更好的方法。