Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

前言

在Spring Data JPA系列的第一篇文章

SpringBoot集成JPA及基本使用-CSDN博客

中讲解了实体类的Id生成策略可以通过@GeneratedValue注解进行配置,该注解的strategy为GenerationType类型,GenerationType为枚举类,支持四种Id的生成策略,分别为TABLE、SEQUENCE、IDENTITY、AUTO,详细信息可以查看第一篇博文。

以上的四种Id生成策略并不能完全满足实际的项目需要,如在分布式系统中,为了实现Id的唯一性,可以采用雪花算法,此时可以使用自定义Id生成策略。

自定义Id生成策略

1.1 自定义Id生成策略

自定义Id生成策略,需要实现org.hibernate.id.IdentifierGenerator接口,重写generate()方法。此处以时间戳作为id为例,代码如下:

package com.jingai.jpa.util;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

import java.io.Serializable;

public class GeneratePK implements IdentifierGenerator {
    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        return System.currentTimeMillis();
    }
}

1.2 引用自定义Id生成策略

自定义Id生成策略使用,代码如下:

package com.jingai.jpa.dao.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
@Table(name = "tb_goods")
public class GoodsEntity2 {

    @Id
    // 指定id生成策略
    @GenericGenerator(name = "generatePk", strategy = "com.jingai.jpa.util.GeneratePK")
    // generator的值为@GenericGenerator的name
    @GeneratedValue(generator = "generatePk")
    private Long id;

    private String name;
    private String subtitle;
    private Long classificationId;
    private Date createTime;

    @Transient
    private String createTimeStr;

}

其他的代码同使用系统提供的Id生成策略一致。

复合主键配置

有些表会存在多个id,如角色功能表。针对这种情况,Spring Data JPA中该如何配置呢?

在Spring Data JPA的实体类中并不支持简单的直接在多个属性中添加@Id注解。而是需要先创建一个复合主键类,然后在实体类中使用@IdClass注解将主键类附加在类中。

下面以会员统计表为例,建表语句:

CREATE TABLE `tb_member_statistics`  (
  `member_id` int(0) NOT NULL,
  `type` int(0) NOT NULL,
  `total_integral` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`member_id`, `type`) USING BTREE
)

该表以member_id、type为复合主键。

2.1 添加复合主键类

复合主键类为主键字段。代码如下:

package com.jingai.jpa.dao.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class MemberStatisticsPk implements Serializable {

    private long memberId;
    private int type;

}

2.2 在实体类中使用@IdClass注解附加复合主键类

package com.jingai.jpa.dao.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;

@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
// 附加主键类
@IdClass(MemberStatisticsPk.class)
@Table(name = "tb_member_statistics")
public class MemberStatisticsEntity {

    @Id
    private long memberId;
    @Id
    private int type;
    private int totalIntegral;
}

2.3 Repository类

Spring Data JPA的Repository接口格式为Repository<T, ID>,其中T为实体类,ID为实体类中的Id。如果要使用Repository原生的ById接口,则必须传入正确的实体类Id。对于复合主键的实体类,此处传入的Id为复合主键类。代码如下:

package com.jingai.jpa.dao;

import com.jingai.jpa.dao.entity.MemberStatisticsEntity;
import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;

import java.util.List;

public interface MemberStatisticsRepository extends JpaRepositoryImplementation<MemberStatisticsEntity, MemberStatisticsPk> {

    @Query("from MemberStatisticsEntity where memberId = ?1")
    List<MemberStatisticsEntity> find(long memberId);

}

2.4 Service类

package com.jingai.jpa.service;

import com.jingai.jpa.dao.MemberStatisticsRepository;
import com.jingai.jpa.dao.entity.MemberStatisticsEntity;
import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class MemberStatisticsService {

    @Resource
    private MemberStatisticsRepository memberStatisticsRepository;

    public List<MemberStatisticsEntity> find(long memberId) {
        return memberStatisticsRepository.find(memberId);
    }

    /**
     * 使用原生的ById接口时,需要传入复合主键类作为id
     */
    public MemberStatisticsEntity find(MemberStatisticsPk pk) {
        return memberStatisticsRepository.findById(pk).get();
    }

}

2.5 Controller类

package com.jingai.jpa.controller;

import com.jingai.jpa.dao.entity.MemberStatisticsPk;
import com.jingai.jpa.service.MemberStatisticsService;
import com.jingai.jpa.util.ResponseUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Map;

@RestController
@RequestMapping("memberstatistics")
public class MemberStatisticsController {

    @Resource
    private MemberStatisticsService memberStatisticsService;

    @GetMapping("find")
    public Map<String, Object> find(long memberId) {
        return ResponseUtil.success(memberStatisticsService.find(memberId));
    }

    @GetMapping("get")
    public Map<String, Object> findById(long memberId, int type) {
        return ResponseUtil.success(memberStatisticsService.find(new MemberStatisticsPk(memberId, type)));
    }

}

复合主键还可以通过@EmbeddedId和@Embeddable注解,采用嵌入式的方式实现,只是没有那么直观。感兴趣的可以自己百度了解一下。

Auditing使用

Auditing翻译过来是审计和审核,在实际的业务中,经常需要记录某条数据的操作人及操作时间,以及记录操作日志,Spring Data JPA通过注解的方式提供了审计功能的架构实现。

3.1 操作时间及操作人注解

Spring Data JPA提供了4个注解解决数据操作人及操作时间数据的维护。

1)@CreatedBy:创建用户

2)CreatedDate:创建时间

3)@LastModifiedBy:修改的用户

4)@LastModifiedDate:最后一次修改的时间

3.2 实体类Auditing的使用

package com.jingai.jpa.dao.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
// 添加AuditingEntityListener实体监听
@EntityListeners(AuditingEntityListener.class)
@Table(name = "tb_user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String state;

    // 添加审计的注解
    @CreatedBy
    private Long createBy;
    @CreatedDate
    private Date createTime;
    @LastModifiedBy
    private Long modifyBy;
    @LastModifiedDate
    private Date modifyTime;

}

添加AuditingEntityListener实体监听及审计的注解。

3.3 自定义AuditorAware

package com.jingai.jpa.config;

import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.Optional;

@Component
public class AppAuditorAware implements AuditorAware<Long> {

    /**
     * 返回当前的审计员,即要添加在@CreateBy和@LastModifiedBy注解中属性的信息。
     * 此处通过Request中获取用户id。可根据实际项目进行修改,如通过jwt或者security等
     */
    @Override
    public Optional<Long> getCurrentAuditor() {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        Object userId = requestAttributes.getAttribute("userId", RequestAttributes.SCOPE_SESSION);
        if(userId == null)
            return Optional.empty();
        return Optional.of((long)userId);
    }
}

3.4 在SpringBoot的启动类中添加@EnableJpaAuditing注解

package com.jingai.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
// 指定扫描的表映射实体Entity的目录,如果不指定,会扫描全部目录
//@EntityScan("com.jingai.jpa.dao.entity")
// 指定扫描的表repository目录,如果不指定,会扫描全部目录
//@EnableJpaRepositories(basePackages = {"com.jingai.jpa.dao"})
// 可选,开启JPA auditing能力,可以自动赋值一些字段,比如创建时间、最后一次修改时间等等
@EnableJpaAuditing
public class JpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }

}

3.5 Repository、Service类

Repository、Service类不需要任何改动。

package com.jingai.jpa.dao;

import com.jingai.jpa.dao.entity.UserEntity;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;

public interface UserRepository extends JpaRepositoryImplementation<UserEntity, Long> {

}
package com.jingai.jpa.service;

import com.jingai.jpa.dao.UserRepository;
import com.jingai.jpa.dao.entity.UserEntity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserRepository userRepository;

    public UserEntity save(UserEntity entity) {
        return userRepository.save(entity);
    }

    public UserEntity find(long id) {
        return userRepository.findById(id).get();
    }

}

3.6 Controller类

package com.jingai.jpa.controller;

import com.jingai.jpa.dao.entity.UserEntity;
import com.jingai.jpa.service.UserService;
import com.jingai.jpa.util.ResponseUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.util.Map;

@RestController
@RequestMapping("user")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping("save")
    public Map<String, Object> save(String name) {
        // 模拟审计员
        RequestContextHolder.getRequestAttributes().setAttribute("userId", 1000l, RequestAttributes.SCOPE_SESSION);
        UserEntity entity = new UserEntity();
        entity.setName(name);
        entity.setState("1");
        return ResponseUtil.success(userService.save(entity));
    }

    @PostMapping("update")
    public Map<String, Object> update(long id, String name) {
        // 模拟审计员
        RequestContextHolder.getRequestAttributes().setAttribute("userId", 1001l, RequestAttributes.SCOPE_SESSION);
        UserEntity entity = userService.find(id);
        entity.setName(name);
        return ResponseUtil.success(userService.save(entity));
    }

}

在修改的时候需要特别注意,如果不先通过id获取原记录,那么修改后,createBy和createDate会被修改为null,因为修改时传入的实体类对象没有createBy和createDate的值。

访问上面的两个接口如下:

3.7 @MappedSuperClass的使用

在项目中,可能会有很多的实体类需要记录操作人及操作时间,此时可以定义一个父类,专门记录操作人及操作信息。

3.7.1 创建公共的抽象类

package com.jingai.jpa.dao.entity;

import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Date;

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 添加审计的注解
    @CreatedBy
    private Long createBy;
    @CreatedDate
    private Date createTime;
    @LastModifiedBy
    private Long modifyBy;
    @LastModifiedDate
    private Date modifyTime;
}

3.7.2 在实体类中继承抽象类

package com.jingai.jpa.dao.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Table;

@Data
@Entity
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
@Table(name = "tb_user")
public class UserEntity extends AbstractAuditable {

    private String name;
    private String state;

}

结尾

限于篇幅,Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用就分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详细讲解lua中string.gsub的使用

string.gsub 是 Lua 标准库中的一个函数&#xff0c;用于全局替换字符串中的某些部分。string.gsub 是 Lua 中非常实用的一个函数&#xff0c;它可以用来进行字符串的处理和替换操作。 它的基本语法如下&#xff1a; string.gsub(s, pattern, replacement [, n])s 是要处理的…

鸿蒙开发核心技术都有哪些【都是从零开始】

鸿蒙开发核心技术都有哪些&#xff1f;&#xff1a;【持续1年的时间公关鸿蒙技术】 我们能做哪些呢&#xff1f; 还是从UI业务开始吧 面试题1&#xff1a; 基于STAGE模型项目重构等问题 代理设计模式&#xff0c;业务与架构隔离 中介者模式&#xff0c;和代理设计模式的区别…

项目管理-项目绩效域1/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 1.项目绩效域--整体框架 项目绩效域 重点&#xff1a; ①八大绩效域的含义。 ②八大绩效域的问题和解决方案。 ③八大绩效域与十大管…

Go标准库——Flag库和Log库

一.Flag Go语言内置的flag包实现了命令行参数的解析&#xff0c;flag包使得开发命令行工具更为简单。 1.1 os.Args 如果你只是简单的的想要获取命令行参数&#xff0c;可以像下面代码示例一样使用os.Args来获取命令行参数。 os.Arg实际是一个存储命令行参数的字符串切片([]stri…

Linux最新提权通杀五大绝招(上)

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 Linux 主机权限提升问题是普遍存在的。在Web 服务器、数据库、防火墙、IOT等基础设施中&#xff0c;大部分都运行着Linux 操作系统&#xff0c;鉴于Linux 设备在大量基…

【负载均衡在线OJ项目日记】项目简介

目录 前言 什么是负载均衡 所用的技术和开发环境 所用技术 开发环境 项目的宏观结构 leetcode 结构 结构 编写思路 前言 从C语言的文章到现在Linux网络部分&#xff0c;我已经涉猎了很多知识&#xff1b;终于在今天我要开始搞项目了&#xff0c;通过项目我也可以开始…

鸿蒙OS NEXT的推出,不仅面向App端

华为官方公布6月份的版本为beta版&#xff0c;依然属于开发者测试版&#xff0c;但可以向普通用户开放了。这点和苹果iOS系统测试形式略微相似&#xff1a;6月份开放首个测试版&#xff0c;随后过渡到公测版&#xff0c;最后再和年度新机一起发布正式版系统。 如果按照这个进度…

DNS域名解析服务的部署及优化方案

实验要求: 1.配置2台服务器要求如下&#xff1a; a&#xff09;服务器1&#xff1a; 主机名&#xff1a;dns-master.timinglee.org ip地址&#xff1a; 172.25.254.100 配置好软件仓库 b&#xff09;服务器2&#xff1a; 主机名&#xff1a;dns-slave.timinglee.org ip地址&am…

fero - yolo - mamba:基于选择性状态空间的面部表情检测与分类

fero - yolo - mamba:基于选择性状态空间的面部表情检测与分类 摘要IntroductionRelated work FER-YOLO-Mamba: Facial Expression Detection and Classification Based on Selective State Space 摘要 面部表情识别&#xff08;FER&#xff09;在理解人类情绪线索方面起着关键…

S型曲线的几种设计(图像对比度调节)

一般来讲&#xff0c;图像调色模块都会提供“曲线”工具&#xff0c;这是一个极其灵活的功能&#xff0c;绝大部分的调色都可以通过该工具实现&#xff0c;但是曲线功能的交互相对而言比较复杂。出于简便性和效率方面的考量&#xff0c;调色模块往往还会提供一些具有很强的功能…

Angular中创建和使用服务

Angular中的服务 文章目录 Angular中的服务前言一、创建服务二、使用服务 前言 Angular 服务是 Angular 应用程序中用于封装可重用逻辑的类。服务在应用程序的多个组件之间共享数据和功能&#xff0c;而不依赖于应用程序的UI。服务可以用于诸如数据处理、与后端通信、用户身份…

电脑显示丢失mfc140u.dll怎么修复,总共有7个方法

mfc140u.dll 是一个动态链接库&#xff08;Dynamic Link Library&#xff09;文件&#xff0c;它是Microsoft Foundation Class (MFC)库的一部分&#xff0c;专为使用C编程语言开发Windows应用程序而设计。MFC库由微软提供&#xff0c;作为一个高级的应用程序框架&#xff0c;旨…

通过 Java 操作 redis -- 连接 redis

如果我们想在本地主机上访问 Linux 服务器上的 redis &#xff0c;我们就需要通过 ssh 进行端口转发&#xff0c;推荐看 本地主机访问服务器的Redis -- 配置 ssh 端口转发 通过 Java 操作 redis 已经有大佬创建了很多第三方库&#xff0c;这里我们使用 jedis &#xff0c;因为它…

N7552A是德科技N7552A电子校准件

181/2461/8938产品概述&#xff1a; 更小巧轻便的 2 端口模块&#xff0c;支持 3.5 mm 或 N 型 50 Ω 连接器&#xff0c;能够将校准时间缩短一半 特点 频率范围&#xff1a;直流至 9 GHz 使用 N 型或 3.5 mm 连接器 更小巧轻便的 2 端口电子校准件&#xff08;ECal&#xff…

Linux:进程等待 进程替换

Linux&#xff1a;进程等待 & 进程替换 进程等待wait接口statuswaitpid接口 进程替换exec系列接口 当一个进程死亡后&#xff0c;会变成僵尸进程&#xff0c;此时进程的PCB被保留&#xff0c;等待父进程将该PCB回收。那么父进程要如何回收这个僵尸进程的PCB呢&#xff1f;父…

《Fundamentals of Power Electronics》——示例:Buck-Boost转换器模型变为正则形式

为了说明正则电路模型推导的步骤&#xff0c;让我们将buck-boost转换器的等效电路操作成规范形式。buck-boost转换器的一个小信号交流等效电路如下图所示。 为了将上图所示网络转换成正则形式&#xff0c;需要将所有独立源d(t)转换到左侧&#xff0c;而将所有电感转换到右侧与变…

PHP 匿名函数和闭包在数据结构中的应用

匿名函数和闭包在数据结构处理中的应用php 中的匿名函数和闭包可用于处理数组、链表和队列等数据结构。针对数组&#xff0c;匿名函数可用于过滤元素&#xff1b;针对链表&#xff0c;闭包可用于创建节点&#xff1b;针对队列&#xff0c;匿名函数和闭包可实现 fifo 队列操作。…

css 文字描边

又是抄样式的一天。这次是百度地图。实现了问题和图形描边的效果。 代码&#xff1a; .BMap_scaleTxt.dark {color: #fff;text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; } 效果&#xff1a;

uniapp打包的程序在Xcode中运行到模拟器报错的解决方法

uniapp打包的程序在Xcode中运行到模拟器报错的解决方法 问题描述&#xff1a; Building for iOS-simulator, but linking in object file (/Users/hori/Documents/SDK/SDK/Libs/DCUniRecord.framework/DCUniRecord[arm64][3](PGRecord.o)) built for iOS Linker command fail…

翻译《The Old New Thing》- Does Windows have a limit of 2000 threads per process?

Does Windows have a limit of 2000 threads per process? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050729-14/?p34773 Raymond Chen 2005年07月29日 Windows 是否有一个每个进程2000线程的限制&#xff1f; 简要 文章解释了在 W…