上一节咱们讲解了Hibernate命名策略,从本节咱们开始陆续讲解属性、关系等映射,本节咱们来说讲主键的生成策略。算法
JPA规范支持4种不一样的主键生成策略(AUTO、IDENTITY、SEQUENCE、TABLE),这些策略以编程方式生成主键值或使用数据库功能(例如自动递增或序列),咱们只需将@GeneratedValue注解添加到主键属性上并选择对应的生成策略。数据库
它是默认的生成策略,并容许持久性提供程序选择生成策略,若是使用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 (?, ?, ?, ?)
它是使用数据库序列生成惟一值的方法,它须要其余select语句才能从数据库序列中获取下一个值,但这对大多数应用程序没有性能影响。若是应用程序必须保留大量的新实体,则可使用某些特定于Hibernate的优化来减小语句的数量。性能
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; }
经过上述咱们知道默认生成的序列表名称为hibernate_sequence,当咱们打开会话插入5条数据时,此时序列表中的next_val为6,也就说序列表中的序列Id和表中主键自增的顺序一致,以下:优化
针对主键经过序列号生成的策略还有一个注解@SequenceGenerator,咱们进行以下配置后,此时将生成名为student_seq的序列表。spa
@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后的结果一致。hibernate
@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, 因此提升了性能。咱们来举一个例子来讲明,以下:3d
@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,所以,第一个实体管理器将继续像一、二、3... 10,第二个实体管理器将继续像十一、 十二、13 ... 20,而后提取下一个student_seq.next_val。此时经过控制台所对序列表所生成的SQL语句以下:code
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,此时将抛出以下异常:
该策略是最容易使用的,但从性能角度来看却不是最佳的。它依靠自动递增的数据库列,并容许数据库在每次插入操做时生成一个新值,从数据库的角度来看很是有效,由于对自动增量列进行了高度优化,而且不须要任何其余语句。可是则此方法有一个很大的缺点,Hibernate须要每一个管理实体的主键值,所以必须当即执行insert语句,这样阻止了它使用其余优化技术(例如JDBC批处理)。
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; }
针对该主键生成策略不多使用,因此就很少讲了,它经过在数据库表中存储和更新其当前值来模拟序列,这须要使用悲观锁,该悲观锁将全部事务按顺序排列,这会减慢应用程序的速度,所以,若是数据库支持大多数流行的数据库所支持的序列,则应首选基于序列号的主键生成策略。
@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策略,即便咱们显式指定了主键,此时会被忽略而不会抛出异常,咱们重点介绍了基于序列的主键生成策略,同时咱们也推荐使用基于序列的策略来自动生成主键。好了,本文到此结束,咱们下节见。