注册
Springboot JPA使用达梦数据库时的主键生成策略
专栏/培训园地/ 文章详情 /

Springboot JPA使用达梦数据库时的主键生成策略

归途 2023/12/22 2985 0 0
摘要

1.GeneratedValue

@GeneratedValue
该注解用于声明主键的生成策略,有两个参数:
1.strategy:

  • table:使用特定的数据库表存放主键
  • sequence:根据数据库序列生成主键
  • identity:数据库自动生成主键(主键自增)
  • auto:由程序自动控制(默认)

2.generator:
主键生成器的名称,如strategy使用sequence,则该值与@SequenceGenerator注解的name参数保持一致。

2.测试达梦可用的主键生成策略

不同数据库对strategy的四种取值支持情况不同,如Oracle不支持identity方式,一般使用sequence,mysql一般使用identity。下面测试达梦支持的该参数取值。

2.1strategy=sequence

Person.java关键代码

@Entity @Table(name = "Person") public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "IdSeq") @SequenceGenerator(name="IdSeq", sequenceName="seqPersonId", allocationSize = 1, initialValue = 1) protected long id; @Column(name = "name") protected String name;

该配置会自动在数据库端生成序列和表。
没有提前在数据库创建序列时,根据sequenceName生成序列,自动生成序列的命名规则为:

  • 如果sequenceName值按驼峰命名规则命名,则不同单词之间用下划线连接,如sequenceName="seqPersonId",则生成的序列名称为seq_person_id。
  • 如果sequenceName值全部小写,则数据库中生成序列名与sequenceName值保持一致。
SQL> SELECT ID FROM SYSOBJECTS WHERE TYPE$ = 'SCH' AND NAME = 'SYSDBA'; 行号 ID ---------- ----------- 1 150994945 已用时间: 1.708(毫秒). 执行号:127600. SQL> SELECT NAME, CRTDATE, INFO3 initialValue, INFO4 allocationSize FROM SYSOBJECTS WHERE TYPE$='SCHOBJ' AND SUBTYPE$ = 'SEQ' AND SCHID = '150994945'; 行号 NAME CRTDATE initialValue allocationSize ---------- ---------- -------------------------- -------------------- -------------------- 1 seqpersond 2023-04-13 11:37:32.845000 1 1 SQL> SELECT NAME, CRTDATE, INFO3 initialValue, INFO4 allocationSize FROM SYSOBJECTS WHERE TYPE$='SCHOBJ' AND SUBTYPE$ = 'SEQ' AND SCHID = '150994945'; 行号 NAME CRTDATE initialValue allocationSize ---------- ------------- -------------------------- -------------------- -------------------- 1 seq_person_id 2023-04-13 12:32:31.781000 1

注意:
sequenceName="seqPersonId"时,数据库已存在名称为seqPersonId的序列时不报错,会创建seq_person_id序列。库中存在seq_person_id序列时不再创建,使用已存在的序列。

设置自增序列后,插入时带自增列的情况
save接口

@RequestMapping("/save") @ResponseBody public String save(){ Person person = new Person(); person.setId(101); person.setName("张三"); personService.save(person); return "插入成功"; }

hibernate 日志

Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from person person0_ where person0_.id=?
Hibernate: select seq_person_id.nextval
Hibernate: insert into person (name, id) values (?, ?)

不报错,但手动设置的person.setId(101)不会生效,id值为序列自动生成。

SQL> select *from person; 行号 id name ---------- -------------------- ---- 1 1 张三 2 2 张三 已用时间: 0.722(毫秒). 执行号:127603.

2.2strategy=identity

Person.java关键代码

@Entity @Table(name = "Person") public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY,generator = "IdSeq") // @SequenceGenerator(name="IdSeq", sequenceName="seqPersonId", allocationSize = 1, initialValue = 1) protected long id; @Column(name = "name") protected String name;

使用DmDialect-for-hibernate5.0驱动时启动会报错如下:

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: org.hibernate.dialect.identity.IdentityColumnSupportImpl does not support identity key generation

需要使用DmDialect-for-hibernate5.3驱动。
自动生成的表结构

CREATE TABLE "SYSDBA"."person" ( "id" BIGINT IDENTITY(1, 1) NOT NULL, "name" VARCHAR(255), NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

测试插入时指定自增列
save接口

@RequestMapping("/save") @ResponseBody public String save(){ Person person = new Person(); person.setId(101); person.setName("张三"); personService.save(person); return "插入成功"; }

表数据,即strategy=identity时,插入时指定自增列不会报错,也不会生效,自增列实际值还是自增生成。

SQL> select *from person; 行号 id name ---------- -------------------- ---- 1 1 张三 已用时间: 0.993(毫秒). 执行号:127604.

从日志可以看到hibernate操作时直接省略了id列的插入。

Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from person person0_ where person0_.id=?
Hibernate: insert into person (name) values (?)

2.3strategy=auto

@Entity @Table(name = "Person") public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO,generator = "IdSeq") // @GeneratedValue(strategy = GenerationType.IDENTITY,generator = "IdSeq") // @SequenceGenerator(name="IdSeq", sequenceName="seqPersonId", allocationSize = 1, initialValue = 1) protected long id; @Column(name = "name") protected String name;

自动生成的表结构

CREATE TABLE "SYSDBA"."person" ( "id" BIGINT NOT NULL, "name" VARCHAR(255), NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

hibernate日志。此时自动生成一个名为generator参数值的序列,即id_seq,其初始值与步长均为1.

Hibernate: create table person (id bigint not null, name varchar(255), primary key (id))
Hibernate: create sequence id_seq increment by 1 start with 1

插入测试
插入或不插入自增列时,都使用序列值

Person person = new Person(); // person.setId(101); person.setName("张三"); personService.save(person); return "插入成功"; }
Hibernate: select id_seq.nextval
Hibernate: insert into person (name, id) values (?, ?)
SQL> select *from person; 行号 id name ---------- -------------------- ---- 1 1 张三 已用时间: 0.859(毫秒). 执行号:127608.

但插入带自增列,且自增列唯一时,hibernate在插入前向数据库查询有无该ID值,即便最终自增列值仍使用序列填充。

Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from person person0_ where person0_.id=?
Hibernate: select id_seq.nextval
Hibernate: insert into person (name, id) values (?, ?)

同样,同名序列存在时,使用已存在序列的定义,不再重新创建。

2.4strategy=table

City.java配置

@Entity @Table(name = "Person") public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "idGenerator01") // @GeneratedValue(generator = "idGenerator01") // @GenericGenerator(strategy = "guid", name = "idGenerator01") // @GeneratedValue(strategy = GenerationType.IDENTITY,generator = "IdSeq") // @SequenceGenerator(name="IdSeq", sequenceName="seqPersonId", allocationSize = 1, initialValue = 1) protected long id; @Column(name = "name") protected String name;

hibernate日志。这种方式会为每张存在自增列的表创建一张辅助表,当前创建id_generator02表存放自增列信息

Hibernate: create table city (id varchar(255) not null, name varchar(255), primary key (id))
Hibernate: create table id_generator01 (sequence_name varchar(255) not null, next_val bigint, primary key (sequence_name))
Hibernate: insert into id_generator01(sequence_name, next_val) values ('person',0)
Hibernate: create table id_generator02 (sequence_name varchar(255) not null, next_val bigint, primary key (sequence_name))
Hibernate: insert into id_generator02(sequence_name, next_val) values ('city',0)
Hibernate: create table person (id bigint not null, name varchar(255), primary key (id))

查看id_generator01表结构和数据,next_val列为bigint,则使用此种方式时,自增列数据类型必须为数值型。

CREATE TABLE "SYSDBA"."id_generator01" ( "sequence_name" VARCHAR(255) NOT NULL, "next_val" BIGINT, NOT CLUSTER PRIMARY KEY("sequence_name")) STORAGE(ON "MAIN", CLUSTERBTR) ;

表数据

SQL> select *from id_generator01; 行号 sequence_name next_val ---------- ------------- -------------------- 1 person 50 已用时间: 2.046(毫秒). 执行号:127613.

此种方式在插入时带自增列会忽略列值,使用辅助表生成的值。

2.5不添加GeneratedValue注解

Person.java @Entity @Table(name = "Person") public class Person implements Serializable { @Id // @GeneratedValue(strategy = GenerationType.IDENTITY,generator = "IdSeq") // @SequenceGenerator(name="IdSeq", sequenceName="seqPersonId", allocationSize = 1, initialValue = 1) protected long id; @Column(name = "name") protected String name;

表结构

CREATE TABLE "SYSDBA"."person" ( "id" BIGINT NOT NULL, "name" VARCHAR(255), NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

插入测试
save方法

@RequestMapping("/save") @ResponseBody public String save(){ Person person = new Person(); // person.setId(101); person.setName("张三"); personService.save(person); return "插入成功"; }

插入时不设置自增列,不报错,自动插入值为0,第二次插入时,提示id重复报错。

SQL> select *from person; 行号 id name ---------- -------------------- ---- 1 0 张三 已用时间: 0.886(毫秒). 执行号:127605.

插入时带自增列则插入附带的参数值

@RequestMapping("/save") @ResponseBody public String save(){ Person person = new Person(); person.setId(101); person.setName("张三"); personService.save(person); return "插入成功"; }
SQL> select *from person; 行号 id name ---------- -------------------- ---- 1 101 张三 已用时间: 0.781(毫秒). 执行号:127607.

2.6使用hibernate中DefaultIdentifierGeneratorFactory的内置类

hibernate的中内置了一些生成ID的类,如下:

public DefaultIdentifierGeneratorFactory(boolean ignoreBeanContainer) { this.generatorStrategyToClassNameMap = new ConcurrentHashMap(); this.ignoreBeanContainer = ignoreBeanContainer; this.register("uuid2", UUIDGenerator.class); this.register("guid", GUIDGenerator.class); this.register("uuid", UUIDHexGenerator.class); this.register("uuid.hex", UUIDHexGenerator.class); this.register("assigned", Assigned.class); this.register("identity", IdentityGenerator.class); this.register("select", SelectGenerator.class); this.register("sequence", SequenceStyleGenerator.class); this.register("seqhilo", SequenceHiLoGenerator.class); this.register("increment", IncrementGenerator.class); this.register("foreign", ForeignGenerator.class); this.register("sequence-identity", SequenceIdentityGenerator.class); this.register("enhanced-sequence", SequenceStyleGenerator.class); this.register("enhanced-table", TableGenerator.class); }

使用guid生成id

@Id @GeneratedValue(generator = "idGenerator02") @GenericGenerator(strategy = "guid", name = "idGenerator02") protected String id;

生成的表结构

CREATE TABLE "SYSDBA"."city" ( "id" VARCHAR(255) NOT NULL, "name" VARCHAR(255), NOT CLUSTER PRIMARY KEY("id")) STORAGE(ON "MAIN", CLUSTERBTR) ;

hibernate日志

Hibernate: select GUID()
2023-04-13 15:02:48.212  WARN 18748 --- [nio-8080-exec-1] org.hibernate.id.GUIDGenerator           : HHH000113: GUID identifier generated: 936E6753DAB44D5345A4B9BA1A98D89F
Hibernate: insert into city (name, id) values (?, ?)

表数据

SQL> select *from city; 行号 id name ---------- -------------------------------- ---- 1 936E6753DAB44D5345A4B9BA1A98D89F 张三 已用时间: 1.790(毫秒). 执行号:127609.

插入时指定ID列值,还是使用hibernate生成的guid

Hibernate: select city0_.id as id1_0_0_, city0_.name as name2_0_0_ from city city0_ where city0_.id=?
Hibernate: select GUID()
2023-04-13 15:12:05.735  WARN 24268 --- [nio-8080-exec-1] org.hibernate.id.GUIDGenerator           : HHH000113: GUID identifier generated: 4995BFC6259246FFF2EDDDB6D4A778A4
Hibernate: insert into city (name, id) values (?, ?)
@RequestMapping("/citySave") @ResponseBody public String save(){ City city = new City(); city.setId("101eab"); city.setName("张三"); cityService.save(city); return "插入成功"; }
SQL> select *from city; 行号 id name ---------- -------------------------------- ---- 1 4995BFC6259246FFF2EDDDB6D4A778A4 张三 已用时间: 0.535(毫秒). 执行号:127610.

使用其他内置类时同理, 修改strategy=类名即可。

3测试总结

3.1汇总

@GeneratedValue注解的strategy参数四个取值达梦均支持,具体如下:

自动生成自增列值 创建序列(已存在是否重建) 自增列插入(报错) 创建辅助表 其他
strategy=sequence 是(不重建) 不生效(不报错)
strategy=identity 不生效(不报错)
strategy=auto 是(不重建) 不生效(不报错)
strategy=table 不生效(不报错) 需要ID列为数值型
不添加注解 是(自增列插入为0) 生效(不报错)
使用内置类 不生效(不报错) 部分内置类需要ID列为字符型

3.2注意事项

1.使用生成序列的取值类型时,如果库中已存在同名序列则不再替换,使用已存在的序列定义。
2.strategy=identity需要使用DmDialect-for-hibernate5.3。
3.strategy=table时ID列必须为数值型。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服