【Spring JPA总结】@GeneratedValue注解介绍

在使用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注解和@GeneratedValuegenerator

关于@SequenceGenerator的在线文档:
https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/sequencegenerator,它包含很多属性,其中示例有用到的:

  • name:自定义的名字,主要是为了另一个注解@GeneratedValuegenerator使用。
  • 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@GeneratedValuegenerator

  • 首先是需要定义@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自带的自增器,它是单机版的,并不是分布式的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容