Oracle序列和主键映射

前言

每张表都有主键,可分别简单主键和组合主键,简单主键为表中的一列,组合主键为表中的几列。主键的生成策略有许多种,其中,序列是Oracle常见的主键生成策略之一。本文主要讲解JPA映射主键的技术细节。其中,主键生成策略是oracle序列,JPA实现是hibernate。

oracle序列

创建序列

[sql]  view plain  copy
  1. drop sequence testsequence;  
  2. Create sequence testsequence Increment by 1 Start with 1 NOMAXVALUE NOMINVALUE Nocycle nocache;  

序列testsequence,从1开始递增,每次递增1。不指定最大值、最小值、不循环、不缓存。

创建表

[sql]  view plain  copy
  1. create table TEST  
  2. (  
  3.   id   NUMBER not null,  
  4.   name VARCHAR2(30)  
  5. )  
  6. ;  
  7. alter table TEST  
  8.   add constraint PK_ID primary key (ID);  
  9.   
  10. create table TEST2  
  11. (  
  12.   id   NUMBER not null,  
  13.   name VARCHAR2(30)  
  14. )  
  15. ;  
  16. alter table TEST2  
  17.   add constraint PK_TEST2_ID primary key (ID);  

创建了两张表,TEST、TEST2。表结构完全一致,简单主键ID,名字name。

创建触发器

[sql]  view plain  copy
  1. CREATE OR REPLACE TRIGGER "TG_TEST"   
  2. BEFORE INSERT ON TEST FOR EACH ROW  WHEN (new.id is nullbegin  
  3. select testsequence.nextval into:new.id from dual;  
  4. end;  
  5.   
  6. /  
  7. ALTER TRIGGER "TG_TEST" ENABLE;  
  8.   
  9.   
  10. CREATE OR REPLACE TRIGGER "TG_TEST2"   
  11. BEFORE INSERT ON TEST2 FOR EACH ROW  WHEN (new.id is nullbegin  
  12. select testsequence.nextval into:new.id from dual;  
  13. end;  
  14.   
  15. /  
  16. ALTER TRIGGER "TG_TEST" ENABLE;  

定义两张表TEST、TEST2的主键生成策略是序列testsequence。

JPA主键映射

注解

使用JPA映射主键,若主键的生成策略是序列的话,需要使用到四个注解,分别是@Id、@Column、@SequenceGenerator、@GeneratedValue。注解@Id表示该实体属性对应数据库的主键字段,注解@Column表示该实体属性对应数据的字段的名称,@SequenceGenerator定义一个序列, @GeneratedValue应用一个序列。
[java]  view plain  copy
  1. private long id;  
  2.   
  3.     @Id  
  4.     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")  
  5.     @SequenceGenerator(name="sequence", sequenceName="testSequence", initialValue=1, allocationSize=1)  
  6.     @Column(name = "ID")  
  7.     public long getId()  
  8.     {  
  9.         return this.id;  
  10.     }  
  11.   
  12.     public void setId(long id)  
  13.     {  
  14.         this.id = id;  
  15.     }  
@SequenceGenerator,属性name,表示序列定义的名称,属性sequenceName,表示序列的名称。这两个属性的解释很像,但并非一回事。属性name指的是序列定义本身的名称,属性sequenceName表示该定义涉及到的序列的名称。属性initialValue,表示序列初始值,属性allocationSize,表示序列递增或递减的幅度。
@GeneratedValue,属性strategy,表示主键生成策略,GenerationType.SEQUENCE表示主键生成策略是序列,属性generator,表示应用序列定义的名称。@GeneratedValue的属性generator和@SequenceGenerator的属性name保持一致。现在,执行持久化动作。
[java]  view plain  copy
  1. Test test = new Test();  
  2. test.setName("John Wiley & Sons");  
  3. manager.persist(test);  
查询数据库,数据的情况如下:


根据源代码,注解@SequenceGenerator的属性initialValue、 allocationSize为可选项,默认值分别是1、50。
[java]  view plain  copy
  1. /**  
  2.     * (Optional) The value from which the sequence object  
  3.     * is to start generating. 
  4.     */  
  5.    int initialValue() default 1;  
  6.   
  7.    /** 
  8.     * (Optional) The amount to increment by when allocating  
  9.     * sequence numbers from the sequence. 
  10.     */  
  11.    int allocationSize() default 50;  
但是,根据测试,结果并非如源代码展示的那样。以上序列重新建立、清空表TEST的数据,把@SequenceGenerator的属性initialValue、 allocationSize去掉,保持默认。执行持久化实体的动作。
查询数据库,数据的情况如下:
Oracle序列和主键映射_第1张图片
若是再一次重新建立以上序列,清空表TEST的数据,重启web server,执行实体持久化实体的工作,数据库的ID并不一定从100开始。也就是说initiaValue的值是随机的,allocationSize也并非是50。而且,从插入表中的主键数值看来,已经脱离了序列的运作。从上图可以看出,序列testSequence的LAST_NUMBER 是3,但是程序插入的主键数值却是100,101。因此,建议在写注解@SequenceGenerator时,虽然属性 initialValue、 allocationSize是可选的,但要明确为这两个属性指定数值,并保持和数据库对序列的定义完全一致。

应用范围

根据源代码,在持久化单元内,序列定义是全局。在一个实体内定义的序列定义,可以应用于持久化单元内的其他实体。
[java]  view plain  copy
  1. /** 
  2.  * Defines a primary key generator that may be referenced by name when 
  3.  * a generator element is specified for the {@link GeneratedValue} 
  4.  * annotation. A sequence generator may be specified on the entity 
  5.  * class or on the primary key field or property. The scope of the 
  6.  * generator name is global to the persistence unit (across all 
  7.  * generator types). 
  8.  * 
  9.  * 
     
  10.  *   Example: 
  11.  * 
  12.  *   @SequenceGenerator(name="EMP_SEQ", allocationSize=25) 
  13.  *  
  14.  * 
  15.  * @since Java Persistence 1.0 
  16.  */  
但是,根据测试,在持久化单元内,序列定义只针对定义它的实体有效。在实体TEST内定义了一个序列定义,名称为sequence。现在在实体TEST2应用这个序列定义。
[java]  view plain  copy
  1. private long id;  
  2.   
  3.   @Id  
  4.   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")  
  5.   @Column(name = "ID")  
  6.   public long getId()  
  7.   {  
  8.       return this.id;  
  9.   }  
  10.   
  11.   public void setId(long id)  
  12.   {  
  13.       this.id = id;  
  14.   }  
启动web server,报出错误Unknown Id.generator: sequence。
[java]  view plain  copy
  1. private long id;  
  2.   
  3.     @Id  
  4.     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")  
  5.     @SequenceGenerator(name="sequence", sequenceName="testSequence", initialValue=1, allocationSize=1)  
  6.     @Column(name = "ID")  
  7.     public long getId()  
  8.     {  
  9.         return this.id;  
  10.     }  
  11.   
  12.     public void setId(long id)  
  13.     {  
  14.         this.id = id;  
  15.     }  
以上代码,在实体TEST2内,针对序列testSequence,重新定义了序列定义,持久化一切正常。因此,序列定义只针对定义它的实体有效。在生产环境中,不同的表应该使用不同的序列。这样才能最大限度保证序列产生的数值在特定的表中,其主键数值具有连续性。比如说,一个序列产生的数值类似1、2、3、4、5......在该序列同时应用在表TEST,TEST2,有可能数值1、2分配给了TEST,而把3、4、5分配给了TEST2,这样看起来,表中的主键数值就常常没有连续性。当然,序列只分配给某个表,也不能保证该表的主键数值就一定会有连续性。当遇到事务回滚时,序列产生的数值同样没有插入到表的主键,造成主键数值的不连续。

你可能感兴趣的:(Oracle序列和主键映射)