在使用Spring Jpa
的時候,除了可以使用Repository提供的一系列自定義的方法(如findBy*)之外,在Entity層使用了很多Java Persistence API
相關(guān)的注解。比如熟知的@Entity
, @Table
, @GeneratedValue
等。
關(guān)于Java Persistence API塑崖,官網(wǎng):https://jakarta.ee/specifications/persistence/
本文嘗試解答@GeneratedValue
用法文黎,以及與@SequenceGenerator
奏路、@GenericGenerator
的區(qū)別。
1. @GeneratedValue介紹
基于Java persistence API v2.2版本的在線Java文檔:https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/generatedvalue
@GeneratedValue注解的主要作用是:聲明主鍵的生成策略臊诊。很自然的鸽粉,它需要和@Id
結(jié)合使用。
這個注解有兩個參數(shù):
1. strategy:
- TABLE:使用一個特定的數(shù)據(jù)庫表格存放主鍵抓艳。
- SEQUENCE:根據(jù)底層數(shù)據(jù)庫的序列來生成主鍵触机,條件是數(shù)據(jù)庫支持序列。(Oracle)
- IDENTITY:主鍵有數(shù)據(jù)庫自動生成(主要是自動增長類型)玷或。(MySQL)
- AUTO:主鍵由程序控制儡首。(默認)
2. generator:
主鍵生成器的名稱,與@SequenceGenerator
偏友、@GenericGenerator
等注解結(jié)合使用蔬胯。
2. 示例
2.1 示例-1:MySQL - strategy=IDENTITY
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
...
}
這時候在測試的時候,就算set了一個id位他,也沒有用氛濒,存入的時候還是會算數(shù)據(jù)庫自增的id來产场。
MySQL主健設(shè)置的是自增長,如果在實體類里不加@GeneratedValue
舞竿,在save的時候京景,id也能自動存入。但有個問題骗奖,就是這時候不小心在程序里set了這個實體的id确徙,那么就不會自動增長了,就會直接將set的這個id存進去了执桌。
總結(jié)鄙皇,如果使用的是自增長,那么還是要聲明strategy = GenerationType.IDENTITY
比較好仰挣。
2.2 示例-2:Oracle - strategy=SEQUENCE
首先在Oracle中定義sequence:
參考:ORACLE SEQUENCE 詳解
create sequence COURSE_SEQ;
然后結(jié)合使用@SequenceGenerator
注解和@GeneratedValue
的generator
育苟。
關(guān)于@SequenceGenerator
的在線文檔:
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/sequencegenerator,它包含很多屬性椎木,其中示例有用到的:
- name:自定義的名字违柏,主要是為了另一個注解
@GeneratedValue
的generator
使用。 - sequenceName香椎,Oracle表中定義的sequence名字漱竖。
- allocationSize - 每次主鍵值增加的大小,例如設(shè)置成1畜伐,則表示每次創(chuàng)建新記錄后自動加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);
}
}
運行兩遍慎框,結(jié)果:可以看到ID是set了什么良狈,它就save什么。
2.4 示例-4: 結(jié)合使用@GenericGenerator與@GeneratedValue的generator
第2.3的示例笨枯,每次id需要手動set薪丁,感覺也不是很方便,改進的辦法是結(jié)合使用@GenericGenerator
與@GeneratedValue
的generator
馅精。
- 首先是需要定義
@GenericGenerator
注解严嗜,name可以隨便取,strategy是ID的實現(xiàn)類洲敢,下面有介紹漫玄。 - 其次還是需要
@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實現(xiàn)類:需要實現(xiàn)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了。程序會自動調(diào)用IdGenerator的generate方法在save前把id給設(shè)置上家凯。測試結(jié)果跟#2.3類似缓醋。
@Test
public void test() {
Course course = new Course();
course.setName("Test");
course.setStatus(StatusEnum.Active);
courseRepository.save(course);
}
2.5 示例-5,@GenericGenerator已經(jīng)存在的內(nèi)置類
根據(jù)文章Difference between @GeneratedValue and @GenericGenerator
的介紹绊诲,Hibernate在類DefaultIdentifierGeneratorFactory預(yù)先內(nèi)置了很多生成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預(yù)置的值generator name了掂之。
以下是示例:
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue(generator = "testGenerator")
@GenericGenerator(name = "testGenerator", strategy = "uuid2")
private String id;
注:如果使用strategy=“increment”這類hibernate自帶的自增器抗俄,它是單機版的,并不是分布式的世舰。