【发布时间】:2011-01-18 15:03:12
【问题描述】:
我的项目的一个要求是记录谁(哪个员工)更新了什么(从什么版本到什么版本)。 UI 需要显示谁在什么时间更新了实体 X 以及更新了什么。
什么是最简洁的实现方式?
出于讨论目的,想象一下……帐户有一个联系人,我需要存储谁更新了联系人、何时以及更新了什么。
【问题讨论】:
标签: hibernate orm coldfusion versioning
我的项目的一个要求是记录谁(哪个员工)更新了什么(从什么版本到什么版本)。 UI 需要显示谁在什么时间更新了实体 X 以及更新了什么。
什么是最简洁的实现方式?
出于讨论目的,想象一下……帐户有一个联系人,我需要存储谁更新了联系人、何时以及更新了什么。
【问题讨论】:
标签: hibernate orm coldfusion versioning
有一个单独的历史记录表来跟踪:
使用触发器更新它,您不必担心任何事情。
或研究一个名为"change data capture" 的主题。一些数据库供应商,如 Oracle,已将其内置到他们的产品中。你只需要打开它。也许你的已经有了。
【讨论】:
我为我正在审核的每个表保留一个单独的表。表名相同,架构不同。示例:dbo.Usr = Audit.Usr。 Audit.Usr 包括 2 个新字段:一个新的主键和一个日期/时间字段。
然后我使用触发器。我不同意有人说使用触发器会污染数据模型。如果规则需要跟踪对数据库的更改,那么将规则放入数据库似乎是合适的地方。正如 Paul Nielsen 在 SQL Server 圣经中所说:任何不在数据库级别强制执行的规则都不是规则,它们只是建议。
这是一个我正在审核对 usr 表的更改的示例。巧合的是,我们正在跟踪更改了 Usr 表的 UsrID,因此有一个名为 Usr_UsrID 的字段。在除了 Usr 表之外的任何其他表中,名为 Usr_UsrID 的字段会更有意义。
IF EXISTS (SELECT * FROM sys.schemas WHERE name = N'Audit')
DROP SCHEMA Audit
GO
CREATE SCHEMA Audit AUTHORIZATION dbo
GO
CREATE TABLE Audit.AuditType(
AuditTypeID Int Identity(1,1) Constraint AuditTypeID Primary Key,
AuditTypeName Varchar(128),
AuditTypeDesc Varchar(128),
AuditTypeSort Int default 0
)
GO
INSERT INTO Audit.AuditType(AuditTypeName,AuditTypeSort,AuditTypeDesc) VALUES('Insert',1,'Insert')
INSERT INTO Audit.AuditType(AuditTypeName,AuditTypeSort,AuditTypeDesc) VALUES('Change',2,'Old Value')
INSERT INTO Audit.AuditType(AuditTypeName,AuditTypeSort,AuditTypeDesc) VALUES('New Value',3,'New Value')
INSERT INTO Audit.AuditType(AuditTypeName,AuditTypeSort,AuditTypeDesc) VALUES('Delete',4,'Delete')
GO
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'dbo.AuditInsert_Usr'))
DROP TRIGGER dbo.AuditInsert_Usr
GO
CREATE Trigger AuditInsert_Usr ON dbo.Usr AFTER Insert
NOT FOR REPLICATION AS
INSERT INTO Audit.Usr(AuditUsr_AuditTypeID,
UsrID,UsrName,UsrPassword,Usr_UsrID)
SELECT 1,
UsrID,UsrName,UsrPassword,Usr_UsrID
FROM Inserted
GO
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'dbo.AuditUpdate_Usr'))
DROP TRIGGER dbo.AuditUpdate_Usr
GO
CREATE Trigger AuditUpdate_Usr ON dbo.Usr AFTER Update
NOT FOR REPLICATION AS
INSERT INTO Audit.Usr(AuditUsr_AuditTypeID,
UsrID,UsrName,UsrPassword,Usr_UsrID)
SELECT 2,
Deleted.UsrID,Deleted.UsrName,Deleted.UsrPassword,Deleted.Usr_UsrID
FROM Inserted
JOIN Deleted
ON Inserted.UsrID = Deleted.UsrID
WHERE Inserted.UsrName <> Deleted.UsrName
OR Inserted.UsrPassword <> Deleted.UsrPassword
OR Inserted.Usr_UsrID <> Deleted.Usr_UsrID;
INSERT INTO Audit.Usr(AuditUsr_AuditTypeID,
UsrID,UsrName,UsrPassword,Usr_UsrID)
SELECT 3,
Inserted.UsrID,Inserted.UsrName,Inserted.UsrPassword,Inserted.Usr_UsrID
FROM Inserted
JOIN Deleted
ON Inserted.UsrID = Deleted.UsrID
WHERE Inserted.UsrName <> Deleted.UsrName
OR Inserted.UsrPassword <> Deleted.UsrPassword
OR Inserted.Usr_UsrID <> Deleted.Usr_UsrID;
GO
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'dbo.AuditDelete_Usr'))
DROP TRIGGER dbo.AuditDelete_Usr
GO
CREATE Trigger AuditDelete_Usr ON dbo.Usr AFTER Delete
NOT FOR REPLICATION AS
INSERT INTO Audit.Usr(AuditUsr_AuditTypeID,
UsrID,UsrName,UsrPassword,Usr_UsrID)
SELECT 4,
UsrID,UsrName,UsrPassword,Usr_UsrID
FROM Deleted
GO
【讨论】:
我们可以使用以下描述的方法通过触发器跟踪更改 @cf_PhillipSenn 和 @duffymo 在他们的回答中。
这给我们留下了一个问题,即要知道每个更改是由哪个用户进行的。每当应用程序打开数据库连接时,我们只需要调用一个存储过程来将应用程序用户 ID 映射到数据库会话 ID。
然后触发器可以从会话 ID 中获取用户 ID。
在 Hibernate 中,我们可以通过提供我们自己的org.hibernate.connection.ConnectionProvider 在打开连接后调用该过程来确保为每个新连接调用此过程。这个类很可能是DatasourceConnectionProvider.java 或DriverManagerConnectionProvider 的子类。
CF-ORM 或许可以通过 ormconfig property 做到这一点。
对于 SQL Server,会话表和存储过程的用户如下所示:
CREATE TABLE UserToSPID (
SPID int PRIMARY KEY,
UserId int
)
CREATE PROCEDURE dbo.[UserToSPID_Register]
@UserId int
AS
delete from UserToSPID where spid=@@spid;
insert into UserToSPID (SPID,userid) values (@@spid,@userid);
GO
【讨论】: