实体类的主键生成策略
1. Oracle自增长主键策略:GenerationType.SEQUENCE
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JPA_USER_S")
@SequenceGenerator(sequenceName = "JPA_USER_S", name = "JPA_USER_S", allocationSize = 1)
private Long id;
解释:
- 当生成策略为
SEQUENCE
时,@GeneratedValue
要配合@SequenceGenerator
使用 - 首先
@Id
代表它下方的属性是实体类的主键属性,也就是数据库的主键; - 其次
@Column(name = "ID")
代表此实体类属性对应的数据库的列名是什么,需要进行关系映射; - 再看
@SequenceGenerator(sequenceName = "JPA_USER_S", name = "JPA_USER_S", allocationSize = 1)
,它代表着需要从数据库找到一个序列并在java中映射。sequenceName
属性值:数据库中的序列名;name
属性值:这个序列名在java中要映射成的名字;allocationSize
属性值:这个序列的自增长步长是几;
- 最后看
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JPA_USER_S")
,它代表着这个主键采取什么样的生成策略。strategy
属性值:采取的主键策略是什么;generator
属性值:使用的序列的映射名是什么,这个映射名就是@SequenceGenerator
中name
的值;
请注意
@SequenceGenerator
和@GeneratedValue
的关系。
2. MYSQL自增主键策略:Generation.IDENTITYT
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
**解释: **
- 当生成策略为
IDENTITY
时,@GeneratedValue
单独使用; @GeneratedValue(strategy = GenerationType.IDENTITY)
,只需要这一个注解就可以实现mysql的主键自增长,我们知道mysql建表的时候可以给主键声明auto_increment
,这就实现了自增长了,所以注解也相对简单。
在
@GeneratedValue
注解中我们只需要生成策略为IDENTITY
,即可完成mysql数据库的主键自增长。
3. 万能自增长主键策略:GenerationType.TABLE
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "sequence_table")
@TableGenerator(name = "sequence_table",
allocationSize = 1,
table = "sequence_table",
pkColumnName = "sequence_name",
valueColumnName = "sequence_count")
private Long id;
解释:
- 当生成策略为
TABLE
时,@GeneratedValue
要配合@TableGenerator
使用; - @TableGenerator(name = “id_sequence”, allocationSize = 1, table = “sequence_table”,pkColumnName = “sequence_max_id”, valueColumnName = “sequence_count”),它代表着需要在数据库建立一张索引表来帮助我们实现主键自增 :
name
属性值:建立的索引表在java中要映射成的名字;allocationSize
属性值:这个序列的自增长步长是几;- table
属性值:建立的序列表的表名,缺省值:
SEQUENCE; - pkColumnName
属性值:建立的序列表的第一个列的列名,此列自动填充需要序列作为主键自增长的表的表名,缺省值:
SEQ_NAME; - valueColumnName
属性值:建立的序列表的第二个列的列名,此列自动填充
pkColumnName所代表的表的下一个序列值,缺省值:
SEQ_COUNT;
@GeneratedValue(strategy = GenerationType.TABLE, generator = "id_sequence")
,代表着这个主键采取什么样的生成策略,和Oracle中的解释一样 :strategy
属性值:采取的主键策略是什么;generator
属性值:使用的映射名是什么,这个映射名就是@SequenceGenerator
中name
的值;
例子
3.1 建立实体类
@Data
@Entity
@Table(name = "JPA_DEPARTMENT")
@EntityListeners(AuditingEntityListener.class)
public class JpaDepartment {
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "sequence_table")
@TableGenerator(name = "sequence_table",
allocationSize = 1,
table = "sequence_table",
pkColumnName = "sequence_name",
valueColumnName = "sequence_count")
private Long id;
@Column(name = "NAME")
private String name;
@Column(name = "OBJECT_VERSION" )
@Version
private Long objectVersion;
@Column(name = "CREATED_BY")
@CreatedBy
private String createdBy;
@Column(name = "CREATED_DATE")
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createdDate;
@Column(name = "LAST_UPDATED_BY" )
@LastModifiedBy
private String lastUpdatedBy;
@Column(name = "LAST_UPDATED_DATE" )
@LastModifiedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date lastUpdatedDate;
}
3.2 其它层
respository、service、controller的代码略过,实现的还是和用户类一样的简单的增删查改;
我并没有使用sql来新建一张表,仅仅是建立了一个实体类,这时候前面写的yml配置就开始发挥它的作用了,我们对jpa的配置如下:
yml:
spring:
jpa:
hibernate:
ddl-auto: update #自动更新
show-sql: true #日志中显示sql语句
解释:
-
ddl-auto: create
:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空; -
ddl-auto: create-drop
:启动时根据实体类生成表,程序关闭时表会被删除; -
ddl-auto: update
:启动时会根据实体类生成表,当实体类属性变动的时候,表结构也会更新,在初期开发阶段使用此选项; -
ddl-auto: validate
:启动时验证实体类和数据表是否一致,在数据结构稳定时采用此选项; -
ddl-auto: none
:不采取任何措施;
建议只使用update和none,前者适合初期建表,后者适合建表完成后保护表结构
show-sql: true
,这个属性代表是否开启显示sql语句,为true
我们就可以在每一次对数据库的操作在控制台看到所使用的sql语句了,方便找错,很方便的属性,建议开发时开启,上线后关闭;
ddl-auto: update时,jpa会根据实体类帮助我们创建表~
3.3 实际效果
控制台输出
Hibernate: create table jpa_department (id number(19,0) not null,
created_by varchar2(255 char),
created_date timestamp,
last_updated_by varchar2(255 char),
last_updated_date timestamp,
name varchar2(255 char),
object_version number(19,0),
primary key (id))
Hibernate: create table sequence_table (sequence_name varchar2(255 char) not null, sequence_count number(19,0), primary key (sequence_name))
Hibernate: insert into sequence_table(sequence_name, sequence_count) values ('jpa_department',0)
- 创建了一张名为
jpa_department
的表,各个列名皆为实体类属性名,长度取的默认值; - 创建了一张名为
sequence_table
序列表(table
属性的值):- 列名1:
pkColumnName
的值(sequence_name),代表此行索引值所属表; - 列名2:
valueColumnName
的值(sequence_count),代表下一次插入的索引值(在插入前会提前+1);
- 列名1:
- 根据
@TableGenerator
所在类映射的表名插入了一行数据,分别为('jpa_department',0)
,也就是代表jpa_department
这张表下一次插入的索引值是1(0+1)
;
借助Database工具查看表全貌
缺少的表,jpa通过实体类对表的映射补全了,
新增了我们所需的JPA_DEPARTMENT
表和一张对主键实现自增长的序列表SEQUENCE_TABLE
;
4. AUTO自动判断主键策略(缺省策略)
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
解释:
- 当生成策略为
AUTO
时,@GeneratedValue
单独使用; @GeneratedValue(strategy = GenerationType.AUTO)
,JPA会自己从从Table 策略
,Sequence 策略
和Identity 策略
三种策略中选择合适的主键生成策略;strategy
属性值:主键生成策略,什么都不写即缺省值即为AUTO
;
4.1 JpaDepartment
@Data
@Entity
@Table(name = "JPA_DEPARTMENT")
@EntityListeners(AuditingEntityListener.class)
public class JpaDepartment {
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "NAME")
private String name;
@Column(name = "OBJECT_VERSION" )
@Version
private Long objectVersion;
@Column(name = "CREATED_BY")
@CreatedBy
private String createdBy;
@Column(name = "CREATED_DATE")
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createdDate;
@Column(name = "LAST_UPDATED_BY" )
@LastModifiedBy
private String lastUpdatedBy;
@Column(name = "LAST_UPDATED_DATE" )
@LastModifiedDate
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date lastUpdatedDate;
}
4.2 控制台输出
Hibernate: create table jpa_department (id number(19,0) not null,
created_by varchar2(255 char),
created_date timestamp,
last_updated_by varchar2(255 char),
last_updated_date timestamp,
name varchar2(255 char),
object_version number(19,0),
primary key (id))
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
- 创建了
jpa_department
这张表; - 创建了一个名为
hibernate_sequence
的序列;
显然jpa识别到了我连接的数据库是oracle数据库,所以采取的是SEQUENCE生成策略,它帮助我们建立了一个名为
hibernate_sequence
的序列,但是这个名字显然是无法一眼看出来是属于谁的序列的,不方便对序列的维护,所以不推荐使用auto
策略哦;
总结
-
主键生成策略分为四种:
SEQUENCE
策略、IDENTITY
策略、TABLE
策略、AUTO
策略; -
SEQUENCE
策略适合拥有序列的数据库,比如Oracle; -
IDENTITY
策略适合拥有主键自增长的数据库,比如Mysql; -
TABLE
策略是通过一张序列表来维护主键插入的值的,所以适合所有数据库; -
AUTO
策略是jpa自行判断使用上面三个中的哪一个作为主键生成策略; -
推荐使用
SEQUENCE
和IDENTITY
策略,开发人员应该自行判断使用的是何种数据库,而不是由jpa进行判断。
注入创建人和更新人
字段复习:
-
@Version
:版本号;进行update操作时启动乐观锁,@Version修饰的字段值与数据库中字段值一致才能进行修改; -
@CreatedDate
:创建时间;进行insert操作时,将当前时间插入到@CreatedDate修饰字段中;进行update操作时,会随实体类中的@CreatedDate修饰的字段值进行修改; -
@CreatedBy
:创建人;进行insert操作时,将当前用户名插入到@CreatedBy修饰字段中;进行update操作时,会随实体类中的@CreatedBy修饰的字段值进行修改; -
@LastModifiedDate
:最后一次修改时间;进行update操作时,将当前时间修改进@LastModifiedDate修饰字段中;进行insert操作时,将当前时间插入到@LastModifiedDate修饰字段中; -
@LastModifiedBy
:最后一次修改的修改人;进行update操作时,将当前修改人修改进@LastModifiedBy修饰的字段中;进行insert操作时,将当前用户名插入到@LastModifiedBy修饰字段中。
启动审计:
@EnableJpaAuditing
@SpringBootApplication
public class SpringContextApplication {
public static void main(String[] args) {
SpringApplication.run(SpringContextApplication.class, args);
}
}
@Data
@Entity
@Table(name = "JPA_USER")
@EntityListeners(AuditingEntityListener.class)
public class JpaUser {
//...
}
@EntityListeners
该注解用于指定Entity或者superclass上的回调监听类;AuditingEntityListener
这个类是一个JPA Entity Listener,用于捕获监听信息,当Entity发生新增和更新操作时进行捕获。
当设置完这两个注解后@CreatedDate、@LastModifiedBy这两个注解对于创建时间和修改时间的注入就ok了,但是对创建人、修改人的注入却为null,毕竟jpa并不知道当前是谁在操作数据,需要我们来进行提供;
注入创建人和更新人
那么jpa从哪里得到它想要注入的创建人或者更新人信息呢?
我们需要一个配置类,并且实现
AuditorAware<String>
接口
@Configuration
public class UserAuditor implements AuditorAware<String> {
/**
* 获取当前创建或修改的用户
*
* @return 获取当前创建或修改的用户Uid
*/
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("俺是测试创建者");
}
}
很容易理解的一个配置,它返回一个
Optional<String>
对象,对象内的String值便是创建人和更新人根据实际情况去注入的值。
调用新增接口
分析:
可以看到,createdBy和lastUpdatedBy都进行了自动注入,全部变成了在
UserAuditor
中设置的返回值
调用更新接口
我们手动将UserAuditor
中的返回值改变,重启应用再试试更新用户接口;
分析:
可以看到,我只将name的值改变为orange1-update,其他不变,调用接口后name、objectVersion、lastUpdatedBy、lastUpdatedDate的值被改变了,系统字段的改变很符合我们的需求;
更新时对更新者的注入也是从UserAuditor中拿取的,jpa自动判断是不是更新操作,是就把当前UserAuditor获取到的返回值注入更新人字段中;
总结
- 通过对配置类
UserAuditor(继承了AuditorAware<String>)
进行返回值的逻辑加工,就可以实现jpa对5个系统字段的审计注入; - 通过session、token、cookie等方式可以保存用户的信息,配合jpa审计功能达到自动注入的效果;