在使用Spring Jpa
的时候,除了可以使用Repository提供的一系列自定义的方法(如findBy*)之外,在Entity层使用了很多Java Persistence API
相关的注解。比如熟知的@Entity
, @Table
, @GeneratedValue
等。
关于Java Persistence API,官网:https://jakarta.ee/specifications/persistence/
本文尝试解答@GeneratedValue
用法,以及与@SequenceGenerator
、@GenericGenerator
的区别。
1. @GeneratedValue介绍
基于Java persistence API v2.2版本的在线Java文档:https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/generatedvalue
@GeneratedValue注解的主要作用是:声明主键的生成策略。很自然的,它需要和@Id
结合使用。
这个注解有两个参数:
1. strategy:
- TABLE:使用一个特定的数据库表格存放主键。
- SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。(Oracle)
- IDENTITY:主键有数据库自动生成(主要是自动增长类型)。(MySQL)
- AUTO:主键由程序控制。(默认)
2. generator:
主键生成器的名称,与@SequenceGenerator
、@GenericGenerator
等注解结合使用。
2. 示例
2.1 示例-1:MySQL - strategy=IDENTITY 主键设置自增长
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
...
}
这时候在测试的时候,就算set了一个id,也没有用,存入的时候还是会算数据库自增的id来。
MySQL主健设置的是自增长,如果在实体类里不加@GeneratedValue
,在save的时候,id也能自动存入。但有个问题,就是这时候不小心在程序里set了这个实体的id,那么就不会自动增长了,就会直接将set的这个id存进去了。
总结,如果使用的是自增长,那么还是要声明strategy = GenerationType.IDENTITY
比较好。
2.2 示例-2:Oracle - strategy=SEQUENCE
首先在Oracle中定义sequence:
参考:ORACLE SEQUENCE 详解
create sequence COURSE_SEQ;
然后结合使用@SequenceGenerator
注解和@GeneratedValue
的generator
。
关于@SequenceGenerator
的在线文档:
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/sequencegenerator,它包含很多属性,其中示例有用到的:
- name:自定义的名字,主要是为了另一个注解
@GeneratedValue
的generator
使用。 - sequenceName,Oracle表中定义的sequence名字。
- allocationSize - 每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50.
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@SequenceGenerator(name = "courseSeq", sequenceName = "COURSE_SEQ", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "courseSeq")
private long id;
...
}
2.3 示例-3: MySQL - strategy=AUTO 主键不自增
strategy=AUTO
的意思是主键由程序控制。默认策略就是AUTO,所以在Entity中可以不用定义@GeneratedValue注解,也是可以工作的。
@Entity
@Table(name = "COURSE")
public class Course {
@Id
private long id;
...
}
测试:
我利用Hazelcast来生成分布式ID(当然用Redis或是UUID都可以):
@SpringBootTest
public class CourseRepositoryTest {
@Autowired
private CourseRepository courseRepository;
@Autowired
private HazelcastInstance hazelcastInstance;
@Test
public void test() {
Course course = new Course();
course.setId(hazelcastInstance.getFlakeIdGenerator("courseId").newId());
course.setName("Test");
course.setStatus(StatusEnum.Active);
courseRepository.save(course);
}
}
运行两遍,结果:
插入了两条
可以看到ID是set了什么,它就save什么。
2.4 示例-4: 结合使用@GenericGenerator与@GeneratedValue的generator
第2.3的示例,每次id需要手动set,感觉也不是很方便,改进的办法是结合使用@GenericGenerator
与@GeneratedValue
的generator
。
- 首先是需要定义
@GenericGenerator
注解,name可以随便取,strategy是ID的实现类,下面有介绍。 - 其次还是需要
@GeneratedValue
,这时候可以不用定义strategy了(默认即AUTO),需要定义一个generator,即上面的name。
注:@GenericGenerator
注解不在Java Persistence API
中,而是hibernate
包中的注解。它的主要作用是用来指定一个ID的生成器的自定义类。
以下是示例:
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GenericGenerator(name = "testGenerator", strategy = "com.util.IdGenerator")
@GeneratedValue(generator = "testGenerator")
private long id;
...
}
Generator实现类:需要实现IdentifierGenerator的generate方法。我依旧选择Hazelcast来生成分布式ID:
public class IdGenerator implements IdentifierGenerator {
@Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
HazelcastInstance hazelcastInstance = ApplicationProvider.getBean(HazelcastInstance.class);
return hazelcastInstance.getFlakeIdGenerator("courseId").newId();
}
}
测试,可以看到这时候就不需要set id了。程序会自动调用IdGenerator的generate方法在save前把id给设置上。测试结果跟#2.3类似。
@Test
public void test() {
Course course = new Course();
course.setName("Test");
course.setStatus(StatusEnum.Active);
courseRepository.save(course);
}
2.5 示例-5,@GenericGenerator已经存在的内置类
根据文章Difference between @GeneratedValue and @GenericGenerator
的介绍,Hibernate在类DefaultIdentifierGeneratorFactory预先内置了很多生成ID的类:
public DefaultIdentifierGeneratorFactory() {
register( "uuid2", UUIDGenerator.class );
register( "guid", GUIDGenerator.class ); // can be done with UUIDGenerator + strategy
register( "uuid", UUIDHexGenerator.class ); // "deprecated" for new use
register( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated
register( "assigned", Assigned.class );
register( "identity", IdentityGenerator.class );
register( "select", SelectGenerator.class );
register( "sequence", SequenceStyleGenerator.class );
register( "seqhilo", SequenceHiLoGenerator.class );
register( "increment", IncrementGenerator.class );
register( "foreign", ForeignGenerator.class );
register( "sequence-identity", SequenceIdentityGenerator.class );
register( "enhanced-sequence", SequenceStyleGenerator.class );
register( "enhanced-table", TableGenerator.class );
}
比如使用uuid2来生成ID,这时候@GenericGenerator的strategy值不是一个类名了,而是hibernate预置的值generator name了。
以下是示例:
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue(generator = "testGenerator")
@GenericGenerator(name = "testGenerator", strategy = "uuid2")
private String id;
注:如果使用strategy=“increment”这类hibernate自带的自增器,它是单机版的,并不是分布式的。
网友评论