数据库表
假设我们有一个包含以下两个表的库应用程序:
存储新旧行状态的最佳方式是使用 JSON 列。因此,对于您要启用审计日志记录的每个表,您可以创建一个审计日志表,如下所示:
CREATE TABLE book_audit_log (
book_id BIGINT NOT NULL,
old_row_data JSON,
new_row_data JSON,
dml_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
dml_timestamp TIMESTAMP NOT NULL,
dml_created_by VARCHAR(255) NOT NULL,
PRIMARY KEY (book_id, dml_type, dml_timestamp)
)
- 该
book_id
列存储book
已创建、更新或删除的行的标识符。
- 这是一个 JSON 列,它将在执行 INSERT、UPDATE 或 DELETE 语句之前
old_row_data
捕获记录的状态。book
- 这是一个 JSON 列,它将在执行 INSERT、UPDATE 或 DELETE 语句后
new_row_data
捕获记录的状态。book
- 这
dml_type
是一个枚举列,用于存储创建、更新或删除给定book
记录的 DML 语句类型。
dml_timestamp
存储 DML 语句执行时间戳。
dml_created_by
存储发出 INSERT、UPDATE 或 DELETE DML 语句的应用程序用户。
使用触发器拦截 INSERT、UPDATE 和 DELETE DML 语句
现在,要提供审计日志表,您需要创建以下 3 个触发器:
CREATE TRIGGER book_insert_audit_trigger
AFTER INSERT ON book FOR EACH ROW
BEGIN
INSERT INTO book_audit_log (
book_id,
old_row_data,
new_row_data,
dml_type,
dml_timestamp,
dml_created_by
)
VALUES(
NEW.id,
null,
JSON_OBJECT(
"title", NEW.title,
"author", NEW.author,
"price_in_cents", NEW.price_in_cents,
"publisher", NEW.publisher
),
'INSERT',
CURRENT_TIMESTAMP,
@logged_user
);
END
CREATE TRIGGER book_update_audit_trigger
AFTER UPDATE ON book FOR EACH ROW
BEGIN
INSERT INTO book_audit_log (
book_id,
old_row_data,
new_row_data,
dml_type,
dml_timestamp,
dml_created_by
)
VALUES(
NEW.id,
JSON_OBJECT(
"title", OLD.title,
"author", OLD.author,
"price_in_cents", OLD.price_in_cents,
"publisher", OLD.publisher
),
JSON_OBJECT(
"title", NEW.title,
"author", NEW.author,
"price_in_cents", NEW.price_in_cents,
"publisher", NEW.publisher
),
'UPDATE',
CURRENT_TIMESTAMP,
@logged_user
);
END
CREATE TRIGGER book_delete_audit_trigger
AFTER DELETE ON book FOR EACH ROW
BEGIN
INSERT INTO book_audit_log (
book_id,
old_row_data,
new_row_data,
dml_type,
dml_timestamp,
dml_created_by
)
VALUES(
OLD.id,
JSON_OBJECT(
"title", OLD.title,
"author", OLD.author,
"price_in_cents", OLD.price_in_cents,
"publisher", OLD.publisher
),
null,
'DELETE',
CURRENT_TIMESTAMP,
@logged_user
);
END
MySQL 函数允许我们创建一个 JSON 对象,该JSON_OBJECT
对象采用提供的键值对。
列dml_type
设置为 的值,INSERT
或者UPDATE
值设置为。DELETE
dml_timestamp
CURRENT_TIMESTAMP
该dml_created_by
列设置为@logged_user
MySQL 会话变量的值,该变量先前由应用程序使用当前登录的用户设置:
Session session = entityManager.unwrap(Session.class);
Dialect dialect = session.getSessionFactory()
.unwrap(SessionFactoryImplementor.class)
.getJdbcServices()
.getDialect();
session.doWork(connection -> {
update(
connection,
String.format(
"SET @logged_user = '%s'",
ReflectionUtils.invokeMethod(
dialect,
"escapeLiteral",
LoggedUser.get()
)
)
);
});
测试时间
在book
表上执行 INSERT 语句时:
INSERT INTO book (
id,
author,
price_in_cents,
publisher,
title
)
VALUES (
1,
'Vlad Mihalcea',
3990,
'Amazon',
'High-Performance Java Persistence 1st edition'
)
我们可以看到插入了一条记录,该记录book_audit_log
捕获了刚刚在book
表上执行的 INSERT 语句:
| book_id | old_row_data | new_row_data | dml_type | dml_timestamp | dml_created_by |
|---------|--------------|--------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------|----------------|
| 1 | | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT | 2020-07-29 13:40:15 | Vlad Mihalcea |
更新book
表格行时:
UPDATE book
SET price_in_cents = 4499
WHERE id = 1
我们可以看到,表book_audit_log
上的 AFTER UPDATE 触发器将添加一条新记录book
:
| book_id | old_row_data | new_row_data | dml_type | dml_timestamp | dml_created_by |
|---------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------|----------------|
| 1 | | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT | 2020-07-29 13:40:15 | Vlad Mihalcea |
| 1 | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} | UPDATE | 2020-07-29 13:50:48 | Vlad Mihalcea |
删除book
表格行时:
DELETE FROM book
WHERE id = 1
一条新记录被表book_audit_log
上的 AFTER DELETE 触发器添加到book
:
| book_id | old_row_data | new_row_data | dml_type | dml_timestamp | dml_created_by |
|---------|--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------|----------------|
| 1 | | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | INSERT | 2020-07-29 13:40:15 | Vlad Mihalcea |
| 1 | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 3990} | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} | UPDATE | 2020-07-29 13:50:48 | Vlad Mihalcea |
| 1 | {"title": "High-Performance Java Persistence 1st edition", "author": "Vlad Mihalcea", "publisher": "Amazon", "price_in_cents": 4499} | | DELETE | 2020-07-29 14:05:33 | Vlad Mihalcea |
就是这样!