从零开始 Spring Boot 64:Hibernate 标识符

news2024/9/28 13:27:02

从零开始 Spring Boot 64:Hibernate 标识符

spring boot

图源:简书 (jianshu.com)

Hibernate 中的实体,由标识符(Identitifier)确定了其实体实例的唯一性,这对应于表中的主键。

@Id

对于单一属性作为标识符的情况,可以用@Id注解标注:

@Entity(name = "Person5")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
    @Id
    private Long id;
}

@Id标注的属性类型必须是 Java 基本类型(Primitive)或基本类型的包装器类型(Wrapper Type),除此之外,还包括 StringDateBigDecimalBigInteger

生成标识符

用 JPA 向数据库添加持久数据的时候,需要为实体指定一个标识符。通常并不需要我们手动完成,利用 JPA 的标识符生成器(Identitifier Generator)就可以自动生成一个标识符。

在实体类中,可以用@GeneratedValue指示通过标识符生成器来生成标识符:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @NotBlank
    @Length(max = 45)
    @Column(unique = true)
    private String name;
}

@GeneratedValuestrategy属性指定了用何种方式生成标识符:

  • TABLE,用基础表生成
  • SEQUENCE,用序列生成
  • IDENTITY,用表的自增主键生成
  • UUID,成成UUID
  • AUTO,默认值,根据属性类别自动进行处理

AUTO

当生成策略(Strategy)是AUTO时,如果作为标识符的属性类型是数字(比如Long),JPA 会使用序列生成标识符。具体来说就是生成一个xxx_seq名称的表,该表只保存一行一个字段的值,该值记录用于生成标识符的序列当前值。

比如在这个示例中:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    // ...
}

DDL:

CREATE TABLE `student` (
  `id` bigint NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_7pb8owoegbhhcrpopw4o1ykcr` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `student_seq` (
  `next_val` bigint DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

如果标识符属性类型是 UUID,则使用 UUIDGenerator 生成标识符:

@Entity(name = "Student2")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    // ...
}

这里的 UUID 是java.util.UUID,而非 JPA 的 UUID 类型。

DDL:

CREATE TABLE `student2` (
  `id` binary(16) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_sj36kwoqdheqatod12i8s5qkf` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

注意,这里用于保存 UUID 标识符的id字段类型为binary(16),也就是16字节(byte)。UUID 是32位16进制数组成的,一个字节可以表示8位二进制数,转换一下就可以表示2位16进制数,16个字节正好可以表示32位16进制数。

因为是二进制形式保存,所以用数据库可视化工具(比如 Sqlyog)查看会是乱码。

在控制台打印持久化后的实体示例可以看到类似这样的内容:

Student(id=65087c03-23e3-4d65-9dc4-6be2be8cad48, name=BrusLee)
Student(id=40084952-3742-46d5-a903-245237ed9168, name=icexmoon)
Student(id=817073e3-0c86-453d-8454-e00a0316bf38, name=JackChen)

id 属性中的内容就是生成的UUID。

IDENTITY

使用这种策略,会使用数据库表的自增主键作为标识符:

@Entity(name = "Student3")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

DDL:

CREATE TABLE `student3` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_m4nlv947n7v1mi89yss97orr3` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

TABLE

与 SEQUENCE 类似,不同的是不会单独建表来保存某个表的序列值,而是用一个统一的表。

比如:

@Entity(name = "Student4")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;
	// ...
}

DDL:

CREATE TABLE `student4` (
  `id` bigint NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_265w6kd3gs9mgm2ua0cf8gvoe` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `hibernate_sequences` (
  `sequence_name` varchar(255) NOT NULL,
  `next_val` bigint DEFAULT NULL,
  PRIMARY KEY (`sequence_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

默认情况下不同的实体使用相同的表(默认为hibernate_sequences)中同一个序列值(sequence_name='default')生成标识符,不过这个设置可以手动修改:

@Entity(name = "Student6")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "student-id-generator")
    @TableGenerator(name = "student6-id-generator",
            table = "hibernate_sequences",
            pkColumnName = "sequence_name",
            pkColumnValue = "student6-id",
            valueColumnName = "next_val",
            allocationSize = 1
    )
    private Long id;

    @NotNull
    @NotBlank
    @Length(max = 45)
    @Column(unique = true)
    private String name;
}

@TableGenerator包含以下属性:

  • name,生成器名称,可以在@GeneratedValuegenerator属性中关联。
  • table,保存生成器序列的表名。
  • pkColumnName,用于区分不同序列的列名。
  • pkColumnValue,当前生成器用于区分序列的列值。
  • valueColumnName,保存序列值的列名。
  • allocationSize,每次批量分配的序列跨度,默认为50。

使用这种方式分配标识符的好处是可以批量持久化数据,所以allocationSize默认为50。负面效果是序列消耗很快,如果单次运行程序仅添加了1个实体实例,序列同样会+50。如果要改变这一点,可以设置allocationSize为1。

UUID

在之前介绍 AUTO 策略的时候已经演示过 UUID 类型和 UUID 生成器的用途了。实际上UUID就是一串32位16进制数,所以也可以用字符串类型的标识符保存 UUID:

@Entity(name = "Student5")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Length(max = 36)
    private String id;
	// ...
}

字符串形式的 UUID 除了32位的16进制数,还有4位-构成的分隔符,比如:

2eb1ba80-1b8e-4db7-8a82-6a5721f2e749

因此这里将标识符id的长度设置为36。

JPA 生成的表结构:

CREATE TABLE `student5` (
  `id` varchar(36) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_juaktqxrqswivtnlhdo9mspmc` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

当然,这样做会浪费一些存储空间,但好处是可以用可视化工具查看表的UUID主键。

SEQUENCE

SEQUENCE 生成策略已经在 AUTO 中介绍过了,与 TABLE 类似,同样可以修改默认的 JPA 行为:

@Entity(name = "Student7")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "student7-id-generator")
    @SequenceGenerator(name = "student7-id-generator",
            sequenceName = "student7_seq",
            initialValue = 5,
            allocationSize = 1)
    private Long id;
	// ...
}

现在数据库中会创建一个表student7_seq来保存序列,且初始的值是5而非1,每次分配的跨度也是1而非50。

自定义标识符生成器

可以自定义标识符生成器。自定义标识符生成器需要实现IdentifierGenerator接口,如果需要从外部添加参数,还需要实现Configurable接口:

public class MyGenerator implements IdentifierGenerator, Configurable {
    private String prefix;

    @Override
    public void configure(Type type, Properties parameters, ServiceRegistry serviceRegistry) {
        IdentifierGenerator.super.configure(type, parameters, serviceRegistry);
        prefix = parameters.getProperty("prefix");
    }

    @Override
    public Object generate(SharedSessionContractImplementor session, Object obj) {
        EntityPersister entityPersister = session.getEntityPersister(obj.getClass().getName(), obj);
        String query = String.format("select %s from %s",
                entityPersister.getIdentifierPropertyName(),
                entityPersister.getEntityName());

        Stream<String> ids = session.createQuery(query, String.class).stream();

        Long max = ids.map(o -> o.replace(prefix + "-", ""))
                .mapToLong(Long::parseLong)
                .max()
                .orElse(0L);

        return prefix + "-" + (max + 1);
    }
}

在上面这个示例中,标识符生成器MyGenerator可以接收一个参数作为生成标识符的前缀。生成标识符的规则为,先查询获取实体的主键,并去除前缀后选取最大值,+1后作为分配的下一个 key,然后加上前缀。

这个示例修改自这篇文章。

使用自定义标识符生成器:

@Entity(name = "Student8")
public class Student {
    @Id
    @GeneratedValue(generator = "student8-id-generator")
    @GenericGenerator(name = "student8-id-generator",
            parameters = @Parameter(name = "prefix", value = "student8-id"),
            type = MyGenerator.class)
    private String id;
	// ...
}

测试用例中生成的标识符类似下面这样:

Student(id=student8-id-2, name=BrusLee)
Student(id=student8-id-1, name=icexmoon)
Student(id=student8-id-3, name=JackChen)

复合标识符

多个属性共同作为标识符的方式称为“复合标识符”(Composite Identifiers),这对应表结构中的联合主键。

可以用@Embeddable+@Embedded或者@IdClass定义符合标识符。相关内容可以阅读这篇文章中的联合主键部分。

派生标识符

派生标识符(Derived Identifier)指的是来自别的实体类的标识符,这对应表结构中作为外键的主键。

可以用@MapsId定义派生标识符的来源,比如:

@Entity
public class UserProfile {

    @Id
    private long profileId;
    
    @OneToOne
    @MapsId
    private User user;

    // ...
}

更多关于派生标识符的使用示例可以阅读:

  • 从零开始 Spring Boot 56:JPA中的一对一关系 - 红茶的个人站点 (icexmoon.cn)
  • 从零开始 Spring Boot 57:JPA中的一对多关系 - 红茶的个人站点 (icexmoon.cn)
  • 从零开始 Spring Boot 58:JPA中的多对多关系 - 红茶的个人站点 (icexmoon.cn)

The End,谢谢阅读。

可以从这里获取本文的完整示例代码。

参考资料

  • UUID是如何保证唯一性的? - 知乎 (zhihu.com)
  • Hibernate Inheritance Mapping | Baeldung

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

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

相关文章

Web3与AI:数字时代安全隐私交易的未来

AI&#xff0b;web3&#xff1d;下一个热门赛道&#xff1f; 在数字时代的浪潮中&#xff0c;Web3和人工智能&#xff08;AI&#xff09;成为了两个备受关注的前沿技术。Web3代表着下一代互联网&#xff0c;强调去中心化、透明和用户控制的特点。而人工智能作为一种智能化的技…

【LeetCode】503. 下一个更大元素 II

503. 下一个更大元素 II&#xff08;中等&#xff09; 方法&#xff1a;单调栈 「 对于找最近一个比当前值大/小」的问题&#xff0c;都可以使用单调栈来解决。栈可以很好的保存原始位置&#xff0c;最近影射栈顶。题目要求更大&#xff0c;因此更大即解–出栈&#xff0c;更小…

【leetcode】15.三数之和(python+转为两数之和+去重)

class Solution(object):def threeSum(self, nums):""":type nums: List[int]:rtype: List[List[int]]"""# 思路&#xff1a;转为两数之和# for循环遍历先固定一个数字a&#xff0c;寻找另外两个数字之和-a&#xff08;双指针&#xff09;# 难点…

【高并发内存池】

&#x1f389;项目&#xff1a;高并发内存池 博主主页&#xff1a;桑榆非晚ᴷ 博主能力有限&#xff0c;如果有出错的地方希望大家不吝赐教 给自己打气&#xff1a;成功没有快车道&#xff0c;幸福没有高速路。所有的成功&#xff0c;都来自不倦地努力和奔跑&#xff0c;所有…

自动化测试工具——Fitnesse

1 介绍 是一个完全集成的独立wiki和验收测试框架。 1.1、协作工具 由于FitNesse是一个wiki web服务器&#xff0c;它的入门和学习曲线非常低&#xff0c;这使得它成为一个优秀的工具&#xff0c;可以与业务涉众进行协作。 1.2、测试工具 FitNesse中创建的wiki页面作为测试…

地球万物皆可计算!星图地球智脑引擎重磅发布

人类文明的每一次阶跃&#xff0c;都起源于计算。 1800多年前&#xff0c;在清脆的珠算声中&#xff0c;算盘拨出人类最古老的计算程序&#xff0c;带来农耕文明的繁盛&#xff1b;200多年前&#xff0c;在齿轮的转动间&#xff0c;机械计算机勾勒出第一次工业革命的袅袅蒸汽&…

Leetcode:167. 两数之和 II - 输入有序数组(2023.7.8 每日一题C++)

目录 167. 两数之和 II - 输入有序数组 题目描述&#xff1a; 实现代码与解析&#xff1a; 暴力&#xff08;超时&#xff09; 双指针 原理思路&#xff1a; 二分 原理思路&#xff1a; 167. 两数之和 II - 输入有序数组 题目描述&#xff1a; 给你一个下标从 1 开始的…

【Python编程系列】2、Python解释器

Python解释器 自带IDLE 当我们使用"Install Now"的默认安装方式时,会自动安装一个纯Python下使用Tkinter编写的相当基本的IDE。 什么是IDE?Integrated Development Environment,集成开发环境。 有多基础呢?可以打开看看它的界面就知道了! 编写代码后,点击回车…

Python——— 分支结构循环结构

&#xff08;一&#xff09;语句底层逻辑思维 控制语句&#xff1a;把语句组合成能完成一定功能的小逻辑模块。 分为三类&#xff1a;顺序、选择和循环。 其中&#xff1a; “ 顺序结构 ” 代表 “先执行a&#xff0c;再执行b” 的逻辑。比如&#xff0c;先找个女朋友&…

实验室信息化LIMS如何采集实验室仪器设备数据?

1. 简述 数据采集功能&#xff0c;数据采集工具将以客户端的形式运行在与设备连接的工作站电脑上&#xff0c;主要负责将设备产生的完整的可输出的原始结果提取到LIMS系统&#xff0c;供实验室人员进行报告生成&#xff0c;汇总等操作。 2. 实现方式 仪器数据采集采用LIMS系统…

单摆模型仿真(SMART PLC梯形图实现)

单摆模型详细介绍这里不再赘述,大家可以参看下面文章链接,单摆模型的仿真有助于大家理解分析力学的有关知识,同时模型的实现可以帮助大家更好的理解和运用微分和积分这2个强有力的工具。 单摆模型(博途PLC和Simulink仿真对比)_RXXW_Dor的博客-CSDN博客单摆模型的详细推导公…

深入分析Spring的IoC容器:从底层源码探索

前言&#xff1a; 博主在最近的几次面试中&#xff0c;大中小厂都问到了Spring的ioc容器相关问题&#xff0c;这块知识确实是面试中的重点内容&#xff0c;因此结合所看的书籍&#xff0c;在这篇文章中总结下。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读&#xff0…

Graalvm编译spring boot 3 + jpa 的原生镜像

编译spring boot 3 native jpa的原生镜像 其中涉及版本&#xff1a; maven: 3.5.4 jdk: 17 graalvm: 22.3 springboot jpa: 3.0.8 一、Windows 1、graalvm安装 GraalVM22.3.0安装地址 解压到任意目录后添加JAVA_HOME环境变量 新增path&#xff1a;%JAVA_HOME%与%JAVA_H…

开发日记-凌鲨中微应用的安全性

凌鲨在框架上使用了特别注重安全性的tauri框架&#xff0c;里面所有的权限都需要明确给出。 微应用本质上是静态web页面加上注入的额外能力。额外能力通过tauri的ipc注入和访问http服务。为了保证主服务的稳定性&#xff0c;一些能力我们是以外挂可执行文件的方式&#xff0c;…

Redis过期策略和持久化机制全面揭秘,教你如何合理配置

Redis过期策略 Redis过期策略就是指Redis如何处理设置了过期时间的键值对。Redis的过期策略有两种&#xff1a;定期删除和惰性删除。 定期删除 定期删除&#xff0c;指的是Redis默认每隔100ms就随机抽取一些设置了过期时间的key&#xff0c;检查是否过期&#xff0c;如果过期…

谈谈mysql——主从模式下的同步方式及半同步、MGR的部署方式

MySQL的复制模式 异步复制 MySQL的复制方式默认是异步的&#xff0c;主从复制涉及三个线程 master I/O master I/O线程负责写入Binlog&#xff0c;并将执行结果返给客户端&#xff0c;至于Binlog有没有被IO线程读取&#xff0c;读取后有没有重放&#xff0c;重放有没有成功&…

Linux系统:OpenSSH7.4p升级到9.0p

目录 一、理论 1.ssh 2.OpenSSH 二、实验 1.OpenSSH升级 三、问题 1.远程SSH服务器拒绝X11转发请求 2.sshd服务重启报错 四、总结 一、理论 1.ssh &#xff08;1&#xff09;概念 ssh 是协议&#xff0c;基于22端口的安全协议。 ssh中文解释是安全的shell&#xff…

使用高斯计需要注意哪些事项

高斯计&#xff08;特斯拉计&#xff09;是检测磁体磁感应强度的专用仪器&#xff0c;可以测量永磁材料的表磁&#xff0c;磁路间隙磁场以及磁场发生装置产生的空间磁场测量。高斯计&#xff08;特斯拉计&#xff09;作为一种比较精密的仪器&#xff0c;在使用过程中应注意以下…

【数据挖掘】推荐系统(一):协同过滤

一、说明 推荐系统是一种计算机程序或算法&#xff0c;用于预测用户对特定项目的兴趣度&#xff0c;并根据这些预测向用户提供个性化推荐。这种系统通常使用大量数据来分析用户的行为和偏好&#xff0c;以找出潜在的喜好和兴趣。推荐系统可以应用于电子商务、社交媒体、影视娱乐…

Java 项目 - SpringBoot+Vue的智慧养老系统

文章目录 1.研究背景2. 技术栈3.系统分析4系统设计4.1 软件功能模块设计4.2数据库设计与实现 5系统详细设计5.1系统功能模块5.2后台登录功能5.2.1管理员功能 源码下载地址 1.研究背景 困扰管理层的许多问题当中,智慧养老平台一定是养老平台不敢忽视的一块。但是管理好智慧养老…