使用 JPA、Hibernate 和 Spring Data JPA 进行审计

news2025/1/13 14:11:31

1. 概述

在ORM的上下文中,数据库审计意味着跟踪和记录与持久实体相关的事件,或者只是实体版本控制。受 SQL 触发器的启发,这些事件是对实体的插入、更新和删除操作。数据库审核的好处类似于源版本控制提供的好处。

在本教程中,我们将演示将审核引入应用程序的三种方法。首先,我们将使用标准 JPA 实现它。接下来,我们将看看两个提供自己的审计功能的JPA扩展,一个由Hibernate提供,另一个由Spring Data提供。

下面是我们将在本例中使用的示例相关实体 BarFoo

 

2. JPA审计

JPA 没有显式包含审计 API,但我们可以使用实体生命周期事件来实现此功能。

2.1.@PrePersist,@PreUpdate@PreRemove

在 JPA 实体类中,我们可以指定一个方法作为回调,我们可以在特定实体生命周期事件期间调用该方法。由于我们对在相应的 DML 操作之前执行的回调感兴趣,因此 @PrePersist@PreUpdate和 @PreRemove回调注释可用于我们的目的:

@Entity
public class Bar {
      
    @PrePersist
    public void onPrePersist() { ... }
      
    @PreUpdate
    public void onPreUpdate() { ... }
      
    @PreRemove
    public void onPreRemove() { ... }
      
}Copy

内部回调方法应始终返回void,并且不带任何参数。它们可以具有任何名称和任何访问级别,但不应是静态的。

请注意,JPA 中的@Version注释与我们的主题并不严格相关;它与乐观锁定的关系大于与审计数据的关系。

2.2. 实现回调方法

不过,这种方法存在很大的限制。如 JPA2 规范 (JSR 317) 中所述:

通常,可移植应用程序的生命周期方法不应调用EntityManagerQuery操作、访问其他实体实例或修改同一持久性上下文中的关系。生命周期回调方法可以修改调用它的实体的非关系状态。

在没有审计框架的情况下,我们必须手动维护数据库模式和域模型。对于我们的简单用例,让我们向实体添加两个新属性,因为我们只能管理“实体的非关系状态”。操作属性将存储所执行操作的名称,时间戳属性用于操作时间戳

@Entity
public class Bar {
     
    //...
     
    @Column(name = "operation")
    private String operation;
     
    @Column(name = "timestamp")
    private long timestamp;
     
    //...
     
    // standard setters and getters for the new properties
     
    //...
     
    @PrePersist
    public void onPrePersist() {
        audit("INSERT");
    }
     
    @PreUpdate
    public void onPreUpdate() {
        audit("UPDATE");
    }
     
    @PreRemove
    public void onPreRemove() {
        audit("DELETE");
    }
     
    private void audit(String operation) {
        setOperation(operation);
        setTimestamp((new Date()).getTime());
    }
     
}Copy

如果我们需要将这样的审计添加到多个类中,我们可以@EntityListeners来集中代码:

@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }Copy
public class AuditListener {
    
    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyOperation(Object object) { ... }
    
}Copy

3. 冬眠者

使用Hibernate,我们可以利用拦截器和事件侦听器以及数据库触发器来完成审计。但是ORM框架提供了Envers,一个实现持久类的审计和版本控制的模块。

3.1. 开始使用 Envers

要设置 Envers,我们需要将hibernate-enversJAR 添加到我们的类路径中:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>${hibernate.version}</version>
</dependency>Copy

然后我们添加@Audited注释,要么在 an@Entity(审计整个实体)上,要么在 specific@Columns(如果我们只需要审计特定属性):

@Entity
@Audited
public class Bar { ... }Copy

请注意,BarFoo 之间存在一对多关系。在这种情况下,我们要么还需要审核Foo@Audited在Foo上添加或者在Bar中对关系的属性进行设置@NotAudited

@OneToMany(mappedBy = "bar")
@NotAudited
private Set<Foo> fooSet;Copy

3.2. 创建审计日志表

有几种方法可以创建审核表:

  • hibernate.hbm2ddl.auto设置为创建,创建-删除更新,以便Envers可以自动创建它们
  • 使用 org.hibernate.tool.EnversSchemaGenerator以编程方式导出完整的数据库架构
  • 设置 Ant 任务以生成相应的 DDL 语句
  • 使用 Maven 插件从我们的映射(例如 Juplo)生成数据库模式以导出 Envers 模式(适用于 Hibernate 4 及更高版本)

我们将采用第一条路线,因为它是最直接的,但请注意,使用hibernate.hbm2ddl.auto在生产中并不安全。

在我们的例子中,bar_AUDfoo_AUD(如果我们也将Foo设置为@Audited)表应该自动生成。审核表使用两个字段从实体的表中复制所有审核字段,即 REVTYPE(值为:“0”表示添加,“1”表示更新,“2”表示删除实体)和REV

除此之外,默认情况下将生成一个名为REVINFO的额外表。它包括两个重要字段,REVREVTSTMP,并记录每个修订的时间戳。我们可以猜到,bar_AUD。修订foo_AUD。REV实际上是REVINFO.REV 的外键。

3.3. 配置环境

我们可以像配置任何其他 Hibernate 属性一样配置 Envers 属性。

例如,让我们将审计表后缀(默认为“_AUD”)更改为“_AUDIT_LOG”。以下是我们如何设置相应属性org.hibernate.envers.audit_table_suffix的值:

Properties hibernateProperties = new Properties(); 
hibernateProperties.setProperty(
  "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG"); 
sessionFactory.setHibernateProperties(hibernateProperties);Copy

可用属性的完整列表可以在Envers 文档中找到。

3.4. 访问实体历史记录

我们可以以类似于通过休眠条件 API 查询数据的方式查询历史数据。我们可以使用AuditReader界面访问实体的审计历史记录,我们可以通过打开的EntityManager会话通过AuditReaderFactory 获取:

AuditReader reader = AuditReaderFactory.get(session);Copy

Envers 提供AuditQueryCreator(由AuditReader.createQuery() 返回)以创建特定于审计的查询。以下行将返回在修订版 #2 修改的所有柱线实例(其中bar_AUDIT_LOG。修订版 = 2):

AuditQuery query = reader.createQuery()
  .forEntitiesAtRevision(Bar.class, 2)Copy

以下是我们如何查询Bar 的修订版。这将导致获取所有状态的所有已审核Bar实例的列表:

AuditQuery query = reader.createQuery()
  .forRevisionsOfEntity(Bar.class, true, true);Copy

如果第二个参数为 false,则结果与REVINFO表联接。否则,仅返回实体实例。最后一个参数指定是否返回已删除的 Bar实例。

然后,我们可以使用AuditEntity工厂类指定约束:

query.addOrder(AuditEntity.revisionNumber().desc());Copy

4. 春季数据JPA

Spring Data JPA是一个框架,通过在JPA提供程序的顶部添加额外的抽象层来扩展JPA。该层支持通过扩展 Spring JPA 存储库接口来创建 JPA 存储库。

出于我们的目的,我们可以扩展CrudRepository<T,ID扩展Serializable>,通用CRUD操作的接口。一旦我们创建并将存储库注入另一个组件,Spring Data 将自动提供实现,我们就可以添加审计功能了。

4.1. 启用 JPA 审计

首先,我们希望通过注释配置启用审核。为了做到这一点,我们在@Configuration类中添加@EnableJpaAuditing

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }Copy

4.2. 添加 Spring 的实体回调侦听器

正如我们已经知道的,JPA 提供了@EntityListeners注释来指定回调侦听器类。Spring Data 提供了自己的 JPA 实体侦听器类,AuditingEntityListener。因此,让我们指定Bar实体的侦听器:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }Copy

现在,我们可以在持久化和更新Bar实体时捕获侦听器的审核信息。

4.3. 跟踪创建和上次修改日期

接下来,我们将添加两个新属性,用于将创建日期和上次修改日期存储到我们的 Bar实体。属性由相应的@CreatedDate@LastModifiedDate注释进行批注,并且其值会自动设置:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    private long createdDate;

    @Column(name = "modified_date")
    @LastModifiedDate
    private long modifiedDate;
    
    //...
    
}Copy

通常,我们将属性移动到基类(注释@MappedSuperClass),所有受审计实体都将扩展该基类。在我们的示例中,为了简单起见,我们将它们直接添加到Bar中。

4.4. 使用 Spring 安全性审核更改的作者

如果我们的应用程序使用 Spring 安全性,我们可以跟踪何时进行更改以及更改者:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;
    
    //...
    
}Copy

@CreatedBy@LastModifiedBy批注的列填充有创建或上次修改实体的主体的名称。该信息来自SecurityContext 的身份验证实例。如果我们想自定义设置为注释字段的值,我们可以实现AuditorAware<T>接口:

public class AuditorAwareImpl implements AuditorAware<String> {
 
    @Override
    public String getCurrentAuditor() {
        // your custom logic
    }

}Copy

为了将应用程序配置为使用AuditorAwareImpl查找当前主体,我们声明了一个AuditorAware类型的 bean,使用AuditorAwareImpl 的实例进行初始化,并将 Bean 的名称指定为auditorAwareRef参数的值@EnableJpaAuditing

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {
    
    //...
    
    @Bean
    AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }
    
    //...
    
}Copy

5. 结论

在本文中,我们研究了实现审核功能的三种方法:

  • 纯 JPA 方法是最基本的,包括使用生命周期回调。但是,我们只允许修改实体的非关系状态。这使得@PreRemove回调对我们的目的毫无用处,因为我们在方法中所做的任何设置都将与实体一起删除。
  • Envers是Hibernate提供的成熟审计模块。它是高度可配置的,并且缺乏纯JPA实现的缺陷。因此,它允许我们审核删除操作,因为它记录到实体表以外的表中。
  • Spring Data JPA 方法抽象了使用 JPA 回调的工作,并为审计属性提供了方便的注释。它也准备好与Spring Security集成。缺点是它继承了 JPA 方法的相同缺陷,因此无法审核删除操作。

本文的示例在GitHub 存储库中提供。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shelby American 汽车 NFT 系列来袭!

我们在 The Sandbox 上推出 Shelby NFT 作品集&#xff0c;加入我们吧&#xff01;该系列包含 Carroll Shelby 制造的一些最稀有和最抢手的汽车&#xff0c;也是现实生活中最具收藏价值的汽车。这些汽车构成了最伟大的汽车历史&#xff0c;也是传奇人物 Carroll Shelby 的伟大代…

为什么开源在线表单工具能做好数据管理?

在数字化时代&#xff0c;数据的有效应用和管理可以说是企业的无形资产&#xff0c;做好数据管理既能提升办公效率&#xff0c;又能帮助企业从规律的数字化管理中获取高效的管理策略。那么&#xff0c;什么样的开源在线表单工具可以实现这一目的&#xff1f;对于企业而言&#…

Axure药企内部管理平台+企业内部管理系统平台

这是一款根据药企的需求设计的内部管理系统&#xff0c;此系统主要是针对市场部和销售部的管理&#xff0c;此作品选择了管理员和地区经理两个角色进行了设计&#xff0c; 设计软件&#xff1a;Axure8.1&#xff08;兼容9和10&#xff09; 作品类型&#xff1a;实战原型 其主要…

抓包神器之Charles(绕过代理屏蔽)以及证书校验绕过

简介 大多数进行渗透测试的时候都可以使用burp抓包,但有的app的部分功能会使用okhttp框架,这种框架会使App不使用默认的系统代理,解决方法就是通过proxy的方式走charles,下面是具体使用方法; Charles 是常用的网络封包截取工具, 通过将自己设置成系统的网络访问代{过}{…

11.21SSM-spring 第一天学习总结

1 Spring 是什么&#xff1f; 针对Bean 生命周期进行管理的轻量级容器 IOC : 浅谈IOC--说清楚IOC是什么_ivan820819的博客-CSDN博客_ioc 软件设计六大原则 : 设计模式六大原则 六大设计原则 1.开闭原则 定义&#xff1a;一个软件实体如类、模块和函数应该对扩展开放&a…

JavaScript/uni-app对接海康ISC openapi

JavaScript/uni-app对接海康ISC openapiJavaScript实现HMAC SHA256下载安装使用crypto-js使用签名生成工具参考JavaScript实现HMAC SHA256 Run the code online with this jsfiddle. Dependent upon an open source js library calledhttp://code.google.com/p/crypto-js/.<…

如何将驱动编译为kernel 模块

前言&#xff1a; 本文章目标平台是PC Linux,不包含其他平台。 执行下面的步骤之前&#xff0c;请先编译kernel通过。 linux KO编译 将驱动程序源码集成到Linux内核中&#xff1a; 将驱动源码文件放到drivers/net/wireless并命名 自己简单创建的几个没有任何关联的源文件&…

力扣(LeetCode)30. 串联所有单词的子串(C++)

滑动窗口哈希表 哈希表 tottottot 存 wordswordswords 所有单词的出现次数。 维护滑动窗口&#xff0c;窗口长度 mwm\times wmw &#xff0c; mmm 是单词数量 www是单词长度 &#xff0c; 窗口长度对应可行解的长度。哈希表 wdwdwd 维护滑动窗口内每个单词的出现次数。 维护…

jstack问题定位分析

目录 1、jstack是什么 2、jstack的使用 1、jstack是什么 jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用来打印出给定的java进程ID或者core file或者远程调试服务的java堆栈信息。 主要是用于生成java虚拟机当前时刻的线程快照&#xff0c;线程快照是当前java虚拟机…

记录运行项目的一些报错

一、git pull 报错 There is no tracking information for the current branch. Please specify whic... There is no tracking information for the current branch.Please specify which branch you want to merge with.See git-pull(1) for detailsgit pull <remote>…

身份安全的零信任方法

一、什么是零信任&#xff1f; 零信任是一组不断发展的网络安全范例术语&#xff0c;它将组织的防御措施从静态的、基于网络的边界转移到关注用户、资产和资源。这是一种安全心态&#xff0c;在明确验证之前&#xff0c;每个传入连接都被视为潜在的恶意请求。这个概念是由世界…

【特征选择】基于二元多邻域人工蜂群 (BMNABC) 特征选择问题(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

1.EdgeX实战 Ubuntu18.04搭建运行环境

文章目录前言:1、搭建Ubuntu18.04服务器平台2、安装docker和docker-compose3、运行EdgeX4、使用浏览器访问EdgeX前言: 想着把之前arduino和树莓派搭建的物联网平台迁移到EdgeX上来&#xff0c;原因有二&#xff1a; 不想去造轮子&#xff0c;自己从零开始写一个物联网的框架&…

影响 SEO 的排名优化的因素

我们在做网站SEO运营的时候&#xff0c;经常会遇到没有排名的情况。即使我们每天保持原创内容的更新和发布&#xff0c;也没有用。这时候就要马上检查网站存在哪些问题&#xff0c;及时解决&#xff0c;让我们的日常努力尽快盈利。以下因素按米贸搜排序&#xff0c;影响SEO排名…

凡亿教育嘉立创宠粉福利,9.9元秒杀PCB多层板设计实战特训班

层林浸染&#xff0c;秋意渐浓 随着双11活动的落幕 意味着工程师最忙碌的时候即将结束 然而在这多事之秋&#xff0c;还是项目高峰期 想必很多工程师都很少过好双11吧 这可不行&#xff01;&#xff01;&#xff01; 别人家有的&#xff0c;我们家的工程师都要有&#xff01;为…

pytest学习和使用10-Pytest中的测试用例如何跳过执行?

10-Pytest中的测试用例如何跳过执行&#xff1f;1 引入2 Unittest中的用例跳过3 pytest.mark.skip4 pytest.skip()5 pytest.mark.skipif()6 跳过标记7 pytest.importorskip1 引入 有时候我们需要对某些指定的用例进行跳过&#xff0c;或者用例执行中进行跳过&#xff0c;在Uni…

高项 成本管理论文

4个过程 1&#xff0c;规划成本&#xff1a;为规划、管理、花费和控制项口成本而制定政策、程序和文档的过程。 2&#xff0c;估算成本&#xff1a;对完成项目活动所需资金进行近似估算的过程。 3&#xff0c;制定预算&#xff1a;汇总所有单个活动或工作包的估算成本&…

【面试题】JavaScript面试题详细总结(一)

js基础部分 01 值类型与引用类型 1.1 问法 js判断数据类型&#xff1f;js值类型与引用类型有哪些&#xff1f;值类型与引用类型的区别&#xff1f; 1.2 介绍 JavaScript存储数据两个区域&#xff1a;栈和堆 栈&#xff1a;通常空间是固定的&#xff08;占据空间小、大小固定&…

MySQL基础|数据库存储时间段,数字从指定值递增AUTO_INCREMENT【详细版,建议收藏】

今天&#xff0c;在写SQL语句存储时间时遇到了一些问题&#xff0c;最后成功解决了 mysql基础一、时间字段的格式限制&#xff08;一&#xff09;精确到秒的表达1、错误的表达2、解决方式如下3、查看创建的表&#xff08;二&#xff09;存储一个时间段1、错误的表达语句2、解决…

一个优秀的程序员应该养成哪些好的习惯?

文章目录一、写代码前先想好思路&#xff0c;先规划框架&#xff0c;再到局部实现二、注重代码风格三、注重代码执行效率四、掌握一些编码原则五、解决问题时&#xff0c;对于原理性的问题&#xff0c;不要面向搜索引擎编程。六、注重基础知识的学习&#xff0c;不忙碌跟风新技…