Hibernate入门之主键生成策略详解
前言
上一节我们讲解了Hibernate命名策略,从本节我们开始陆续讲解属性、关系等映射,本节我们来讲讲主键的生成策略。
主键生成策略
JPA规范支持4种不同的主键生成策略(AUTO、IDENTITY、SEQUENCE、TABLE),这些策略以编程方式生成主键值或使用数据库功能(例如自动递增或序列),我们只需将@GeneratedValue注解添加到主键属性上并选择对应的生成策略。
GenerationType.AUTO
它是默认的生成策略,并允许持久性提供程序选择生成策略,如果使用Hibernate作为持久性框架,它将基于数据库特定的Dialect选择生成策略,对于大多数流行的关系数据库,它会选择GenerationType.SEQUENCE生成策略。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
}

此时将生成默认名称为hibernate_sequence的序列表,该序列表只有名为next_val的一列,该列存储的是下一个主键值。也就是说当在对应表中计划添加第一行数据时,此时会向序列表中插入一行数据即next_val等于1,为了数据一致性,然后查询出该next_val值并添加排他锁即(for update),同时更新该next_val等于2,最后向对应表中的主键设置设置为查询出来的next_val值。整个过程生成的SQL语句如下:
insert into hibernate_sequence values ( 1 ) select next_val as id_val from hibernate_sequence for update update hibernate_sequence set next_val= ? where next_val=? insert into Student (email, firstName, lastName, id) values (?, ?, ?, ?)
GenerationType.SEQUENCE
它是使用数据库序列生成唯一值的方法,它需要其他select语句才能从数据库序列中获取下一个值,但这对大多数应用程序没有性能影响。如果应用程序必须保留大量的新实体,则可以使用某些特定于Hibernate的优化来减少语句的数量。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private int id;
}
通过上述我们知道默认生成的序列表名称为hibernate_sequence,当我们打开会话插入5条数据时,此时序列表中的next_val为6,也就说序列表中的序列Id和表中主键自增的顺序一致,如下:

针对主键通过序列号生成的策略还有一个注解@SequenceGenerator,我们进行如下配置后,此时将生成名为student_seq的序列表。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
@SequenceGenerator(name = "student_generator", sequenceName = "student_seq")
private int id;
}

针对@GenerateValue注解,我们知道在默认情况下将会生成名为的hibernate_sequence的序列表且此时对应表的主键自增和序列表中列next_val一致,同时上述我们添加对生成序列号的注解@SequenceGenerator后,此时next_val将为101,这是因为在该注解上有一个名为allocationSize的属性且默认值为50(可修改为负数)。但是若我们去掉该注解,在注解@GeneratedValue上有一个名为generator的属性,我们进行如下显式配置,结果将和上述使用注解@SequenceGenerator后的结果一致。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq") private int id;
}

注解@SequenceGenerator上的allocationSize = N表示:每N个持久调用中一次从数据库中获取下一个值,在此之间将值局部增加1。具体是什么意思呢?通过对allocationSize属性的显式设置,此数字之后将再次进行数据库查询以获取下一个数据库序列值,默认情况下初始化值从1开始,且实体的主键始终将增加1,除非我们达到了该分配大小限制,一旦达到allocationSize后,将再次从数据库序列中检索下一个ID, 所以提高了性能。我们来举一个例子来说明,如下:
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
@SequenceGenerator(name = "student_generator",sequenceName = "student_seq", allocationSize = 10)
private int id;
}
我们将上述allocationSize设置为10,当进行第一个持久调用时,将从数据库中获取student_seq.next_val,随后的持久调用将不会进入到数据库,而是将在本地返回最后一个值+1,也就是说一直在内存中进行,直到该值达到限制10,这样就可以节省9次数据库读取,若有两个实体管理器试图做同一件事怎么办?当第一个实体管理器调用student_seq.next_val时,它将获得1,第二个实体将得到的主键值为11,因此,第一个实体管理器将继续像1、2、3... 10,第二个实体管理器将继续像11、 12、13 ... 20,然后提取下一个student_seq.next_val。此时通过控制台所对序列表所生成的SQL语句如下:
insert into student_seq values ( 1 ) select next_val as id_val from student_seq for update update student_seq set next_val= ? where next_val=? select next_val as id_val from student_seq for update update student_seq set next_val= ? where next_val=?

我们看到上述对序列表的更新只执行了两次SQL操作,第一次则是插入1,然后更新为next_val = 11,第二次则是next_val = 21,具体如何计算想必不用再多讲。如上述所讲,通过设置此属性的大小可减少与数据库序列表的操作,从而提高性能,但是这将引来序列表Id和插入表的主键值Id不一致的问题,比如我们使用纯JDBC,那么获取插入行的下一个ID将是个问题。若将该属性设置为1,虽然解决了这个问题,但是,每次都会执行查询,如果数据库被其他应用程序访问,那么如果另一个应用程序同时使用相同的ID则会产生问题,如此看来,将该属性设置为1并没有什么很大的问题。序列ID的生成始终都是下一次而分配,通过默认值将其保留为50,这很显然太高了,如果我们将在一个会话中保留近50条记录,这些记录将不会持久保存,并且将使用此特定会话和转换来持久保存,那么它也将有所帮助,因此,在使用SequenceGenerator注解时,应始终使用allocationSize = 1, 对于大多数流行的关系数据库而言,序列始终以1递增为最佳。
到这里为止我们详细讨论了序列号策略生成主键的各种配置。默认情况下,生成hibernate_sequence的序列表且该序列表中的next_val和对应表中的主键增长一致,若我们显式配置generator属性,此时将更改序列表名称且此时序列表中的next_val将具有跳跃性,因为这种情况和通过添加注解@SequenceGenerator结果一致(默认allocationSize为50),若需要更改在内存中进行一次持久调用获取下一次序列号Id时,则需要添加注解@SequenceGenerator并显式配置allocationSize大小。那么问题来了,Hibernate针对序列号的生成器又有哪几种方式呢?在Hibernate 5之前针对序列号的生成器策略应该只有两种(具体未考证):SequenceGenerator、SequenceHiLoGenerator,在Hibernate 5中这两种生成器已被弃用,现在只有名为SequenceStyleGenerator一种生成器策略,在该生成器策略下有5种优化器:HILO、LEGACY_HILO、POOLED、POOED_LO、POOED_LOTL,具体请参看包【org.hibernate.id.enhanced】下的枚举StandardOptimizerDescriptor,截图如下:

如下,当我们只是配置了主键的生成为序列号生成策略时,此时为上述枚举none,不会选择任何优化器即此时序列号表的next_val和表主键自增长一致。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE) private int id;
}
当我们针对上述注解@GeneratedValue,如下显式配置generator属性或通过注解@SequenceGenerator显式配置allocationSizes时,此时将采用pooled【池化】优化器来解释allocationSize,换句话说:从Hibernate 5开始,当JPA实体标识符使用的分配大小大于1时,池优化器是Hibernate使用的默认基于序列的策略。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq") }
或者
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_generator")
@SequenceGenerator(name = "student_generator",sequenceName = "student_seq",allocationSize = 3)
}
我们添加5条数据,此时在序列表中的next_val将为10,next_val值生成示意图如下:


若我们需要修改基于池优化器的序列策略,比如将优化器修改成hilo(高低优化器),这个优化器主要是针对高低算法的实现,我们可通过注解@GenericGenerator来指定,如下:
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")
@GenericGenerator(
name = "student_seq",
strategy = "sequence",
parameters = {
@Parameter(name = "sequence_name", value = "student_seq"),
@Parameter(name = "initial_value", value = "1"),
@Parameter(name = "increment_size", value = "3"),
@Parameter(name = "optimizer", value = "hilo")
}
) private int id;
}
注意:在注解@GeneratedValue上通过属性generator显式指定序列表名称时,尽量不要使用英文标点中的句号即【.】,因为Hibernate内置对此符号做了处理。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student.seq") private int id;
}
如上将生成名为seq的序列表,若在上述基础上继续添加【.】,例如修改为student.seq1.seq2,此时将抛出如下异常:

GenerationType.IDENTITY
该策略是最容易使用的,但从性能角度来看却不是最佳的。它依靠自动递增的数据库列,并允许数据库在每次插入操作时生成一个新值,从数据库的角度来看非常有效,因为对自动增量列进行了高度优化,并且不需要任何其他语句。但是则此方法有一个很大的缺点,Hibernate需要每个管理实体的主键值,因此必须立即执行insert语句,这样阻止了它使用其他优化技术(例如JDBC批处理)。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
}
GenerationType.TABLE
针对该主键生成策略很少使用,所以就不多讲了,它通过在数据库表中存储和更新其当前值来模拟序列,这需要使用悲观锁,该悲观锁将所有事务按顺序排列,这会减慢应用程序的速度,因此,如果数据库支持大多数流行的数据库所支持的序列,则应首选基于序列号的主键生成策略。
@Entity
public class Student { @Id
@GeneratedValue(strategy = GenerationType.TABLE,generator = "student_generator")
@TableGenerator(name="student_generator", table="id_generator") private int id;
}
总结
本节我们详细介绍了Hibernate 5.x中关于主键生成的策略,当然我们若不指定注解@GenerateValue,那么主键则需要显式指定,针对IDNENTITY和SEQUENCE策略,即使我们显式指定了主键,此时会被忽略而不会抛出异常,我们重点介绍了基于序列的主键生成策略,同时我们也推荐使用基于序列的策略来自动生成主键。好了,本文到此结束,我们下节见。
Hibernate入门之主键生成策略详解的更多相关文章
- Hibernate主键生成策略详解
转载自:http://blog.csdn.net/wanghuan203/article/details/7562395 hibernate提供的主键生成策略,使我们可以在实体类的映射xml文件中设定 ...
- hibernate框架(4)---主键生成策略
主键生成策略 常见的生成策略分为六种 1.increment 由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的 ...
- Hibernate框架的主键生成策略
在Hibernate中,id元素的<generator>子元素用于生成持久化类的对象的唯一标识符,也就是主键.Hibernate框架中定义了许多主键生成策略类,也叫生成器类.所有的生成器类 ...
- 三 Hibernate持久化状态&主键生成策略
持久化类 持久化:将内存中的一个对象持久化到数据库中的过程 持久化类:Java类+映射文件.Java中一个类与数据库的表建立了映射关系,那么这个类称为持久化类. 持久化类的编写规则: 对持久化类提供一 ...
- Hibernate 表映射 主键生成策略与复合主键
主要分析三点: 一.数据表和Java类的映射 : 二.单一主键映射和主键的生成策略 : 三.复合主键的表映射 : 一.数据表和Java类的映射 Hibernate封装了数据库DDL语句,只需要将数据 ...
- Hibernate的ID主键生成策略
ID生成策略(一) 通过XML配置实现ID自己主动生成(測试uuid和native) 之前我们讲了除了通过注解的方式来创建一个持久化bean外.也能够在须要持久化的bean的包路径下创建一个与bean ...
- hibernate annotation 相关主键生成策略
Hibernate 默认的全面支持 13 物种生成策略 : 1. increment 2. identity 3. sequence 4. hilo 5. seqhilo 6. uuid 7. uu ...
- Hibernate -- Session的主键生成策略
*缓存:集合--集合放置到内存中 * 只要session存在 session的一级缓存肯定存在. *当执行查询时,以oid为oid=1条件到session的一级缓存中查找oi ...
- hibernate 注解 主键生成策略
一.JPA通用策略生成器 通过annotation来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由@GeneratedValue ...
随机推荐
- Java使用Thrift,Thrift结构体定义
1.Thrift定义文件,Thrift常见的数据类型 1.基本类型(括号内为对应的Java类型): bool(boolean): 布尔类型(TRUE or FALSE) byte(byte): 8位带 ...
- linux中cd / cd~ cd cd- 和cd..之间的区别
cd 进入用户主目录 cd ~ 进入用户主目录 cd - 返回进入此目录之前所在目录 cd .. 返回上一级目录 cd ../.. 返回上两级目录 ...
- VMware-workstation虚拟机安装及配置
目录 安装准备 开始安装 设置虚拟机文件默认位置 安装准备 系统环境:Windows10 专业版 软件:VMware-workstation-full-14.0.0.24051.exe 秘钥:FF31 ...
- mui弹出输入法遮住input表单元素
转自https://www.cnblogs.com/devilyouwei/p/6293190.html mui弹出输入法遮住input表单元素 问题如下:当我用mui开发app时,在mui-sc ...
- C/C++预处理指令#include,#define,#undef,#if,#ifdef,#ifndef,#elif,#endif,#error......
本文主要记录了C/C++预处理指令,常见的预处理指令如下: #空指令,无任何效果 #include包含一个源代码文件 #define定义宏 #undef取消已定义的宏 #if如果给定条件为真,则编译下 ...
- EXAM-2018-8-9
EXAM-2018-8-9 B 水题 注意理解题意 有坑 G 博弈 观察发现 总是会进行到最后两个,或者先手取完全部,再特判一下只有一张牌的情况 H 九连环 通过找规律 我们可以得出递推式: F[n] ...
- 网站爬取-案例四:知乎抓取(COOKIE登录抓取个人中心)(第二卷)
接着上卷来分析,作为开发人员我们都知道,登录是一个想指定URL发送POST请求的过程,所以我们需要找到请求的URL,以及字段,先用一个错误账号和密码做一下尝试,如果是正确的话会直接跳转到别的页面,这样 ...
- @EnableWebMvc WebMvcConfigurer CorsConfig
package me.zhengjie.core.config; import org.springframework.context.annotation.Configuration; import ...
- ESTScan|EORF|Augustus|nr|PSM|
生物信息学方法的目的有二:1.常规找鉴定已知蛋白2.鉴定新蛋白 控制数据库大小可以通过增多酶切使得大数据库灵敏性增高数据量变小: 分步搜索是对于经典方法使用后找不到的新蛋白进行补充挖掘,预测蛋白与高可 ...
- 如果你有一个域名,你也可以免费有一个diy@yourdomain.com的企业邮局
如果你有一个域名,例如:www.bengou.net那么你可以拥有一个免费邮箱:lajiyoujian@bengou.net.那么什么是企业邮局呢?有啥优点 企业邮局是指以您的域名作为后缀的电子邮件地 ...