.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rgba(37, 41, 51, 1) }
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { line-height: 1.5; margin-top: 35px; margin-bottom: 10px; padding-bottom: 5px }
.markdown-body h1 { font-size: 24px; line-height: 38px; margin-bottom: 5px }
.markdown-body h2 { font-size: 22px; line-height: 34px; padding-bottom: 12px; border-bottom: 1px solid rgba(236, 236, 236, 1) }
.markdown-body h3 { font-size: 20px; line-height: 28px }
.markdown-body h4 { font-size: 18px; line-height: 26px }
.markdown-body h5 { font-size: 17px; line-height: 24px }
.markdown-body h6 { font-size: 16px; line-height: 24px }
.markdown-body p { line-height: inherit; margin-top: 22px; margin-bottom: 22px }
.markdown-body img { max-width: 100% }
.markdown-body hr { border-top: 1px solid rgba(221, 221, 221, 1); border-right: none; border-bottom: none; border-left: none; margin-top: 32px; margin-bottom: 32px }
.markdown-body code { border-radius: 2px; overflow-x: auto; background-color: rgba(255, 245, 245, 1); color: rgba(255, 80, 44, 1); font-size: 0.87em; padding: 0.065em 0.4em }
.markdown-body code, .markdown-body pre { font-family: Menlo, Monaco, Consolas, Courier New, monospace }
.markdown-body pre { overflow: auto; position: relative; line-height: 1.75 }
.markdown-body pre>code { font-size: 12px; padding: 15px 12px; margin: 0; word-break: normal; display: block; overflow-x: auto; color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.markdown-body a { text-decoration: none; color: rgba(2, 105, 200, 1); border-bottom: 1px solid rgba(209, 233, 255, 1) }
.markdown-body a:active, .markdown-body a:hover { color: rgba(39, 91, 140, 1) }
.markdown-body table { display: inline-block !important; font-size: 12px; width: auto; max-width: 100%; overflow: auto; border: 1px solid rgba(246, 246, 246, 1) }
.markdown-body thead { background: rgba(246, 246, 246, 1); color: rgba(0, 0, 0, 1); text-align: left }
.markdown-body tr:nth-child(2n) { background-color: rgba(252, 252, 252, 1) }
.markdown-body td, .markdown-body th { padding: 12px 7px; line-height: 24px }
.markdown-body td { min-width: 120px }
.markdown-body blockquote { color: rgba(102, 102, 102, 1); padding: 1px 23px; margin: 22px 0; border-left: 4px solid rgba(203, 203, 203, 1); background-color: rgba(248, 248, 248, 1) }
.markdown-body blockquote:after { display: block; content: "" }
.markdown-body blockquote>p { margin: 10px 0 }
.markdown-body ol, .markdown-body ul { padding-left: 28px }
.markdown-body ol li, .markdown-body ul li { margin-bottom: 0; list-style: inherit }
.markdown-body ol li .task-list-item, .markdown-body ul li .task-list-item { list-style: none }
.markdown-body ol li .task-list-item ol, .markdown-body ol li .task-list-item ul, .markdown-body ul li .task-list-item ol, .markdown-body ul li .task-list-item ul { margin-top: 0 }
.markdown-body ol ol, .markdown-body ol ul, .markdown-body ul ol, .markdown-body ul ul { margin-top: 3px }
.markdown-body ol li { padding-left: 6px }
.markdown-body .contains-task-list { padding-left: 0 }
.markdown-body .task-list-item { list-style: none }
@media (max-width: 720px) { .markdown-body h1 { font-size: 24px } .markdown-body h2 { font-size: 20px } .markdown-body h3 { font-size: 18px } }.markdown-body pre, .markdown-body pre>code.hljs { color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.hljs-comment, .hljs-quote { color: rgba(153, 153, 136, 1); font-style: italic }
.hljs-keyword, .hljs-selector-tag, .hljs-subst { color: rgba(51, 51, 51, 1); font-weight: 700 }
.hljs-literal, .hljs-number, .hljs-tag .hljs-attr, .hljs-template-variable, .hljs-variable { color: rgba(0, 128, 128, 1) }
.hljs-doctag, .hljs-string { color: rgba(221, 17, 68, 1) }
.hljs-section, .hljs-selector-id, .hljs-title { color: rgba(153, 0, 0, 1); font-weight: 700 }
.hljs-subst { font-weight: 400 }
.hljs-class .hljs-title, .hljs-type { color: rgba(68, 85, 136, 1); font-weight: 700 }
.hljs-attribute, .hljs-name, .hljs-tag { color: rgba(0, 0, 128, 1); font-weight: 400 }
.hljs-link, .hljs-regexp { color: rgba(0, 153, 38, 1) }
.hljs-bullet, .hljs-symbol { color: rgba(153, 0, 115, 1) }
.hljs-built_in, .hljs-builtin-name { color: rgba(0, 134, 179, 1) }
.hljs-meta { color: rgba(153, 153, 153, 1); font-weight: 700 }
.hljs-deletion { background: rgba(255, 221, 221, 1) }
.hljs-addition { background: rgba(221, 255, 221, 1) }
.hljs-emphasis { font-style: italic }
.hljs-strong { font-weight: 700 }

@[toc]

前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。

本项目为前后端分离开发,后端基于Java21SpringBoot3开发,后端使用Spring SecurityJWTSpring Data JPA等技术栈,前端提供了vueangularreactuniapp微信小程序等多种脚手架工程。

在项目中每条数据在创建或修改的时候,我想记录创建人,创建时间,最后修改人,最后修改时间等审计信息。如果每次都手动赋值,代码会变得冗长,显得很不优雅。Spring Data JPA为我们提供了审计功能,可以在执行创建或修改操作时自动为审计信息赋值。

本文将介绍两种实现方式,关于SpringBoot项目如何整合Spring Data JPA,请参阅blog.csdn.net/kingslave1/…

实现方式

使用Spring Data JPA实现审计功能,主要涉及以下注解:

  • @EnableJpaAuditing,启用JPA审计功能开关。
  • @EntityListeners,可以监听实体对象的增删改查操作,调用监听器中设置的回调方法。
  • @CreatedBy,创建人,执行insert操作时自动赋值。
  • @CreatedDate,创建日期,执行insert操作时自动赋值。
  • @LastModifiedBy,最后修改人,执行insertupdate操作时自动赋值。
  • @LastModifiedDate,最后修改时间,执行insertupdate操作时自动赋值。

基于AuditorAware接口实现审计功能

启用JPA审计功能

定义一个配置类Bean,启用Spring Data JPA和审计功能,也可以直接main方法所在类上直接添加@EnableJpaRepositories@EntityScan@EnableJpaAuditing注解。

/**
* Spring Data JPA Bean配置
* 启用Jpa,扫描指定包下的Repository类和指定包下的实体类
*/
@Configuration
@EnableJpaRepositories(basePackages = {"com.demo.data.repo"})
@EntityScan(basePackages = "com.demo.data.model")
@EnableJpaAuditing
public class JpaConfig {
}

定义实体类

以定义一个用户类SysUser为例,为其添加@EntityListeners({AuditingEntityListener.class})注解,在其审计信息属性上添加@CreatedBy等注解。

@Getter
@Setter
@Entity
@EntityListeners({AuditingEntityListener.class})
public class SysUser implements Serializable {
/**
* ID,唯一标识列,使用主键自增策略
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 创建时间
*/
@CreatedDate
private LocalDateTime createdTime;
/**
* 最后修改时间
*/
@LastModifiedDate
private LocalDateTime lastModifiedTime;
/**
* 创建人ID
*/
@CreatedBy
private Long creatorId;
/**
* 最后修改人ID
*/
@LastModifiedBy
private Long lastModifierId;
/**
* 用户名
*/
@Column(unique = true)
private String username;
/**
* 密码
*/
private String password;
/**
* 电话
*/
private String phone;
}

实现AuditorAware接口

我们需要定义一个AuditorAware接口的实现类,并将其注册为Bean。

实现AuditorAware<T>接口需要传递类型参数,这个参数应使用创建人(creatorId)最后修改人(lastModifierId)属性的类型,在实体类SysUsercreatorIdlastModifierId的类型是Long,则此处泛型参数也应该为Long

@Component
public class AuditorAwareImpl implements AuditorAware<Long> {
/**
* 返回当前用户ID,insert和update操作会调用该方法自动赋值
*/
@Override
public Optional<Long> getCurrentAuditor() {
// 获取当前用户Id,具体获取逻辑请自行实现
Long userId = 1L;
return Optional.ofNullable(userId);
}
}

至此,审计功能开发完成,可调用SysUser类的Repository接口执行创建或修改操作来测试是否开发成功。

基于自定义监听器实现审计功能

基于AuditorAware接口实现审计功能虽然简单,但也存在不适用的场景,例如创建人属性不仅要记录创建人的用户ID,还需要记录创建人的用户名时;或者开发者希望仅在insert操作时执行某些操作。
针对上述场景,可以考虑使用自定义实体操作监听器的方式实现,实现步骤如下。

启用JPA审计功能

定义一个配置类Bean,启用Spring Data JPA和审计功能,也可以直接main方法所在类上直接添加@EnableJpaRepositories@EntityScan@EnableJpaAuditing注解。

/**
* Spring Data JPA Bean配置
* 启用Jpa,扫描指定包下的Repository类和指定包下的实体类
*/
@Configuration
@EnableJpaRepositories(basePackages = {"com.demo.data.repo"})
@EntityScan(basePackages = "com.demo.data.model")
@EnableJpaAuditing
public class JpaConfig {
}

定义实体类

以定义一个用户类SysUser为例,为其添加@EntityListeners({AuditingEntityListener.class, CustomEntityAuditingListener.class})注解,在其审计信息属性上添加@CreatedBy等注解。
与1.2一节中给出的实体类相比,新增了creatorlastModifier两个属性,@EntityListeners注解中多了一个CustomEntityAuditingListener,在下一节中笔者将给出CustomEntityAuditingListener的实现代码。

@Getter
@Setter
@Entity
@EntityListeners({AuditingEntityListener.class, CustomEntityAuditingListener.class})
public class SysUser implements Serializable {
/**
* ID,唯一标识列,使用主键自增策略
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 创建时间
*/
@CreatedDate
private LocalDateTime createdTime;
/**
* 最后修改时间
*/
@LastModifiedDate
private LocalDateTime lastModifiedTime;
/**
* 创建人ID
*/
@CreatedBy
private Long creatorId;
/**
* 创建人用户名
*/
private String creator;
/**
* 最后修改人ID
*/
@LastModifiedBy
private Long lastModifierId;
/**
* 最后修改人用户名
*/
private Long lastModifier;
/**
* 用户名
*/
@Column(unique = true)
private String username;
/**
* 密码
*/
private String password;
/**
* 电话
*/
private String phone;
}

定义自定义监听器类

自定义监听器会用到以下几种注解,可以监听数据库操作的不同时机。

  • @PostLoad,实体对象查询之后
  • @PrePersist,实体对象保存之前
  • @PostPersist,实体对象保存之后
  • @PreUpdate,实体对象修改之前
  • @PostUpdate,实体对象修改之后
  • @PreRemove,实体对象删除之前
  • @PostRemove,实体对象删除之后

以下是CustomEntityAuditingListener类的实现代码,使用了@PrePersist@PreUpdate两个注解。

public class CustomEntityAuditingListener {
@PrePersist
private void prePersist(BaseEntity entity) {
// 获取当前用户,具体获取逻辑请自行实现
SysUser current = new SysUser(); entity.setCreatorId(current.getId());
entity.setCreator(current.getUsername());
entity.setLastModifierId(current.getId());
entity.setLastModifier(current.getUsername());
} @PreUpdate
private void preUpdate(BaseEntity entity) {
// 获取当前用户,具体获取逻辑请自行实现
SysUser current = new SysUser(); entity.setLastModifierId(current.getId());
entity.setLastModifier(current.getUsername());
}
}

至此,审计功能开发完成,可调用SysUser类的Repository接口执行创建或修改操作来测试是否开发成功。

总结

本文介绍了两种使用Spring Data JPA实现审计功能的方法,实现AuditorAware接口和自定义实体操作监听器,可以在创建或修改数据时自动为审计信息赋值,减少了冗长的手动赋值代码,如有错误,还望批评指正。

在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。

使用Spring Data JPA实现审计功能,记录创建人、创建时间、最后修改时间和最后修改人的更多相关文章

  1. Spring Data JPA的Audit功能,审计数据库的变更

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 数据库审计 数据库审计是指当数据库有记录变更时,可以记录数据库的变更时间和变更人等,这样以后出问题回溯问责也比较方便. ...

  2. spring data jpa auditing

    审计日志,记录实体版本的修改信息,创建时间,修改时间,创建人,修改人等 可以采用切片AOP的方式实现,也可以通过Spring Data JPA的审计功能实现 切片方式 Spring AOP中Point ...

  3. Spring Data JPA(官方文档翻译)

    关于本书 介绍 关于这本指南 第一章 前言 第二章 新增及注意点 第三章 项目依赖 第四章 使用Spring Data Repositories 4.1 核心概念 4.2 查询方法 4.3 定义rep ...

  4. Spring Boot 入门系列(二十七)使用Spring Data JPA 自定义查询如此简单,完全不需要写SQL!

    前面讲了Spring Boot 整合Spring Boot JPA,实现JPA 的增.删.改.查的功能.JPA使用非常简单,只需继承JpaRepository ,无需任何数据访问层和sql语句即可实现 ...

  5. spring data jpa自定义baseRepository

    在一些特殊时候,我们会设计到对Spring Data JPA中的方法进行重新实现,这将会面临一个问题,如果我们新创建一个实现类.如果这个实现类实现了JpaRepository接口,这样我们不得不实现该 ...

  6. 12 Spring Data JPA:springDataJpa的运行原理以及基本操作(上)

    spring data jpaday1:orm思想和hibernate以及jpa的概述和jpa的基本操作 day2:springdatajpa的运行原理 day2:springdatajpa的基本操作 ...

  7. Spring data jpa 使用技巧记录

    软件152 尹以操 最近在用Springboot 以及Spring data jpa  ,使用jpa可以让我更方便的操作数据库,特开此帖记录使用jpa的一些小技巧. 一.使用spring data j ...

  8. Spring Data JPA 学习记录1 -- 单向1:N关联的一些问题

    开新坑 开新坑了(笑)....公司项目使用的是Spring Data JPA做持久化框架....学习了一段时间以后发现了一点值得注意的小问题.....与大家分享 主要是针对1:N单向关联产生的一系列问 ...

  9. SpringBoot学习笔记:Spring Data Jpa的使用

    更多请关注公众号 Spring Data Jpa 简介 JPA JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR ...

  10. spring data jpa入门学习

    本文主要介绍下spring data jpa,主要聊聊为何要使用它进行开发以及它的基本使用.本文主要是入门介绍,并在最后会留下完整的demo供读者进行下载,从而了解并且开始使用spring data ...

随机推荐

  1. 文心一言 VS 讯飞星火 VS chatgpt (160)-- 算法导论12.4 2题

    二.用go语言,请描述这样一棵有 n 个结点的二叉搜索树,其树中结点的平均深度为 O(lgn),但这棵树的高度是w(lgn).一棵有 n个结点的二叉搜索树中结点的平均深度为 O(lgn),给出这棵树高 ...

  2. Semantic Kernel 正式发布 v1.0.1 版本

    微软在2023年12月19日在博客上(Say hello to Semantic Kernel V1.0.1)发布了Semantic kernel的.NET 正式1.0.1版本.新版本提供了新的文档, ...

  3. 香橙派5plus从ssd启动Ubuntu

    官方接口图 我实际会用到的就几个接口,背面的话就一个M.2固态的位置: 其中WIFI模块的接口应该也可以插2230的固态,不过是pcie2.0的速度,背面的接口则是pcie3.0*4的速度,差距还是挺 ...

  4. MySQL 事务的基础知识

    事务的基础知识 1. 数据库事务概述 事务是数据库区别于文件系统的重要特性之一,当我们有了事务就会让数据库中的数据始终保持 一致性,同时我们还能通过事务的机制 恢复到某个时间地点的数据,这样可以保证已 ...

  5. CVE-2016-5734 复现

    CVE-2016-5734 漏洞简介 phpMyAdmin 4.0.x-4.6.2 远程代码执行漏洞(CVE-2016-5734) phpMyAdmin是一套开源的.基于Web的MySQL数据库管理工 ...

  6. 轻量化动态编译库 Natasha v8.0 正式发布!

    .NET8.0 与 动态编译 Hello 各位小伙伴,我于 2024年1月10日 发布了 Natasha 一个全新的里程碑版本 v8.0,对于老用户而言,此次发布版本号跨度较大,是因为我决定使用新的版 ...

  7. Spring表达式语言(SPEL)学习(01)

    算术运算 @Test public void test01() { // 定义解析器 ExpressionParser parser = new SpelExpressionParser(); // ...

  8. Java 将PPT转为OFD

    本文以Java后端程序代码展示如何实现将PPT幻灯片转成OFD格式.下面是具体步骤. 步骤1:安装PPT库-Spire.Presentation for Java 方法一.通过Maven仓库安装.在p ...

  9. 华为云GaussDB亮相金融业数据库技术大会

    本文分享自华为云社区<华为云GaussDB亮相金融业数据库技术大会,激发金融行业发展新动能>,作者:GaussDB 数据库 . 近日,由北京金融信息化研究所主办的2023金融业数据库技术大 ...

  10. 如何使用appuploader制作描述文件​

    如何使用appuploader制作描述文件​ 承接上文我们讲述了怎么制作证书,本文我们来看下怎么制作描述文件吧.​制作描述文件前我们首先我们来添加一个测试设备,后面再制作描述文件. 1.添加测试设备​ ...