读书人

实业标识的自动生成

发布时间: 2012-09-01 09:33:02 作者: rapoo

实体标识的自动生成
转自: http://www.ibm.com/developerworks/cn/java/j-lo-openjpa5/

数据的唯一性是所有应用程序非常基本的要求,由开发者或者用户来维护这种唯一性存在着较大的风险,因此,由系统自动产生唯一标识是一种常见的做法。OpenJPA 中支持四种不同的实体标识自动生成策略:
容器自动生成的实体标识;
使用数据库的自动增长字段生成实体标识;
根据数据库序列号(Sequence)技术生成实体标识;
使用数据库表的字段生成实体标识;
这四种方式各有优缺点,开发者可以根据实际情况进行选择。
可选择的注释
要让容器和数据库结合管理实体标识的自动生成,根据实际情况的不同,开发者可以选择 javax.persistence.*包下面的 GeneratedValue、SequenceGenerator、TableGenerator三个注释来描述实体的标识字段。
@javax.persistence.GeneratedValue
每一个需要自动生成实体标识的实体都需要为它的实体标识字段提供 GeneratedValue注释和相应的参数,OpenJPA 框架会根据注释和参数来处理实体标识的自动生成。
使用 GeneratedValue注释自动生成的实体标识可以是数值类型字段如 byte、short、int、long等,或者它们对应的包装器类型 Byte、Short、Integer、Long等,也可以是字符串类型。
GeneratedValue注释可以支持两个属性 strategy和 generator。
strategy
strategy是 GenerationType类型的枚举值,它的内容将指定 OpenJPA 容器自动生成实体标识的方式。strategy属性可以是下列枚举值:
GeneratorType.AUTO
表示实体标识由 OpenJPA 容器自动生成,这也是 Strategy 属性的默认值。
GenerationType.IDENTITY
OpenJPA 容器将使用数据库的自增长字段为新增加的实体对象赋唯一值,作为实体的标识。这种情况下需要数据库提供对自增长字段的支持,常用的数据库中,HSQL、SQL Server、MySQL、DB2、Derby 等数据库都能够提供这种支持。
GenerationType.SEQUENCE
表示使用数据库的序列号为新增加的实体对象赋唯一值,作为实体的标识。这种情况下需要数据库提供对序列号的支持,常用的数据库中,Oracle、PostgreSQL 等数据库都能够提供这种支持。
GenerationType.TABLE
表示使用数据库中指定表的某个字段记录实体对象的标识,通过该字段的增长为新增加的实体对象赋唯一值,作为实体的标识。
String generator
generator属性中定义实体标识生成器的名称。如果实体的标识自动生成策略不是 GenerationType.AUTO或者 GenerationType.IDENTITY,就需要提供相应的 SequenceGenerator或者 TableGenerator注释,然后将 generator属性值设置为注释的 name属性值。
@javax.persistence.SequenceGenerator
如果实体标识的自动生策略是 GenerationType.SEQUENCE,开发者需要为实体标识字段提供 SequenceGenerator注释,它的参数描述了使用序列号生成实体标识的具体细节。该注释支持以下四个属性:

表 1. SequenceGenerator 注释属性说明
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Animal { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String name; … }

保存 Animal实体的第一个实例时,OpenJPA 框架自动调用 SQL 语句 SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID=0,从默认保存实体标识的 OPENJPA_SEQUENCE_TABLE表中获取实体的标识,如果不存在 ID为 0 的记录,OpenJPA 框架自动将实体的标识设置为 1。

容器管理实体标识的情况下,为了获得实体标识,应用程序将不得不频繁地和数据库交互,这会影响应用程序的运行效率。OpenJPA 中使用实体标识缓存机制解决这个问题。默认情况下,当应用程序第一次获取实体标识时,OpenJPA 框架从数据库中一次性获取 50 个连续的实体标识缓存起来,当下一次应用程序需要获取实体标识时,OpenJPA 将首先检测缓存中是否存在实体标识,如果存在,OpenJPA 将直接使用缓存中的实体标识,如果不存在,OpenJPA 框架将会从数据库中再次获取 50 个连续的实体标识缓存起来,如此类推。这样的处理方式可以大大减少由于获取实体标识而产生的数据库交互,提升应用程序的运行效率。

当实体标识成功获取之后,OpenJPA 框架会把当前实体标识的最大值 +1 后持久化到数据库中。由于实体标识缓存的原因,当我们第一次获取实体标识后,OpenJPA 会将 OPENJPA_SEQUENCE_TABLE表的 SEQUENCE_VALUE的值设置为 51,当 OpenJPA 多次从数据库中获取实体标识后,SEQUENCE_VALUE的值会以 50 为单位递增,变为 101、151、201 …。

OpenJPA 缓存的实体标识不是永久存在的,只能在同一个 EntityManagerFactory管理范围内起作用,也就是说,当获取实体标识的 EntityManagerFactory对象被关闭后,这些被获取的实体标识中没有用掉的那一部分标识就丢失了,这会造成实体标识的不连续。由同一个 EntityManagerFactory对象创建的 EntityManager上下文之间则能够共享 OpenJPA 框架获取的实体标识,这意味着,我们可以使用同一个 EntityManagerFactory对象创建多个 EntityManager对象,用它来持久化实体,然后关闭它,在持久化过程中所需要的实体表示将会使用同一个实体标识的缓存区,因此不会引起实体标识的丢失。
容器管理的实体标识还有一个非常重要的特性:所有被容器管理的实体标识都是共享的。不管 OpenJPA 容器中存在多少个不同的被容器管理的实体标识,它们都会从同一个实体标识缓存中获取实体标识。我们可以用下面的例子说明这种情况:假设 OpenJPA 容器中存在两个实体类 Dog和 Fish,它们的实体标识字段都是数值型,并且都由 OpenJPA 管理。当我们首先持久化一个 Dog对象时,它的实体标识将会是 1,紧接着我们持久化一个 Fish对象,它的实体标识就是 2,依次类推。

uuid-string
要使用 uuid-string 机制自动生成实体标识,我们需要将实体主键字段的 GeneratedValue注释的 strategy属性设置为 GenarationType.AUTO,然后将 GeneratedValue注释的 generator属性设置为 uuid-string。以 Animal 实体类为例,我们只需要将 Animal 实体修改为如下内容:

清单 2. 使用 uuid-string 机制自动生成实体标识

import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Animal {  @Id  @GeneratedValue(strategy=GenerationType.AUTO, generator = "uuid-string") private String id; private String name;   …} 


uuid-hex
要使用 uuid-hex 机制自动生成实体标识,我们必须将实体主键字段的 GeneratedValue注释的 strategy属性设置为 GenarationType.AUTO,然后将 GeneratedValue注释的 generator属性设置为 uuid-hex。以 Animal 实体类为例,我们只需要将 Animal 实体修改为如下内容:

清单 3. 使用 uuid-hex 机制自动生成实体标识

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Animal {
@Id
@GeneratedValue(strategy=GenerationType.AUTO, generator = "uuid-hex")
private String id;
private String name;



}

自增长字段
自增长字段是 HSQL、SQL Server、MySQL、DB2、Derby 等数据库提供的一种特性,用于为数据库的记录提供自动增长的编号,应用程序的设计者通常期望将实体标识的自动生成委托给数据库的这种特性,OpenJPA 框架中的实体标识能够满足应用程序设计者的要求,使用数据库的自增长字段为实体自动生成标识。
要将实体标识的自动生成委托给数据库的自增长字段特性,需要数据库和实体定义的双方配合才能够达到:首先,必须将实体标识字段对应的数据库列修改为自动增长列,另外还需要将实体类中实体标识字段的 GeneratedValue注释的 stragety属性的值设置为 GenerationType.IDENTITY。
我们以 Animal 实体在 HSQL 数据库中的持久化来说明如何使用自增长字段自动生成实体标识所需要采取的步骤:
首先,我们使用下面的 SQL 语句创建 Animal 表,把它的 ID字段设置为自动增长类型:

清单 4. 将 ID 字段设置为自动增长类型的 SQL 语句

 CREATE TEXT TABLE ANIMAL (   ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,   NAME VARCHAR(255) NOT NULL  ) 


在数据库部分将表的主键字段设置为自动增长字段后,在实体 Animal的定义中,我们需要将 id字段 GeneratedValue注释的 stragety属性的值设置为 GenerationType.IDENTITY。Animal 实体类修改后的代码片段如下。

清单 5. 标识由自增长字段生成的 Animal 实体类

import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Animal {  @Id  @GeneratedValue(strategy=GenerationType.IDENTITY) private long id;  private String name;   …} 


序列号(Sequence)
序列号是 Oracle、PostgreSQL 等数据库提供的一种特性,用于为数据库的记录提供自动增长的编号,使用 Oracle、PostgreSQL 等数据库应用程序的设计者通常期望将实体标识的自动生成委托给数据库的这种特性,OpenJPA 框架中的实体标识能够满足应用程序设计者的要求,使用数据库的序列号为实体自动生成标识。

要将实体标识的自动生成委托给数据库的序列号特性,需要数据库和实体定义的双方配合才能够达到:首先,必须在数据库中创建合适的序列号,另外还需要为实体标识字段提供 SequenceGenerator注释,设置它的参数,为实体类提供关于序列号的信息,同时将实体类中实体标识字段的 GeneratedValue注释的 stragety属性的值设置为 GenerationType.SEQUENCE,将 generator属性的值设置为 SequenceGenerator注释的 name属性的值。
我们以 Animal 实体在 Oracle 数据库中的持久化来说明如何使用自增长字段自动生成实体标识所需要采取的步骤:
首先,在 Oracle 数据库中运行下面的 SQL 语句创建名为 HelloWorldSequence的序列号,序列号支持 cache,大小为 50:

清单 6. 创建序列号的 SQL 语句

 CREATE SEQUENCE HELLOWORLDSEQUENCE  START WITH 0  INCREMENT BY 1  MINVALUE 1  CACHE 50  NOCYCLE  NOORDER 


然后,在 Oracle 数据库中,我们使用下面的 SQL 语句创建 ANIMAL表:

清单 7. 创建 ANIMAL 表

CREATE TABLE EOS52.ANIMAL
 (  ID CHAR(10),  NAME VARCHAR2(100) NOT NULL,  CONSTRAINT PK_ANIMAL PRIMARY KEY (ID )  ) 


在数据库部分创建合适的序列号和相应的数据库表后,在实体 Animal的定义中,我们需要将 id字段 GeneratedValue注释的 stragety属性的值设置为 GenerationType.SEQUENCE,设置它的 generator属性的值为 SeqGenerator。我们还需要为 id字段提供另外一个相关的注释 SequenceGenerator,设置它的 name属性为 SeqGenerator,设置它 sequenceName属性为 HelloWorldSequence。Animal 实体类修改后的代码片段如下。

清单 8. 标识由序列号生成的 Animal 实体类

import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;    @Entity public class Animal {     @Id     @GeneratedValue(strategy = GenerationType.SEQUENCE,         generator = "SeqGenerator")    @SequenceGenerator(name = "SeqGenerator",         sequenceName = " HelloWorldSequence")    private long id;     private String name;           …    } 


数据库表
除了使用容器生成的实体标识,或者借助于数据库的自增长字段或者序列号等方式生成实体标识之外,我们还可以选择借助数据库表来自动生成实体标识。原理是我们提供一个独立的数据库表,该表的主键列 ( 假设列名 ID) 记录实体编号的特征字符串 ( 假设存在一条记录的 ID为 customID),另外一列 ( 假设列名为 SEQUENCE_VALUE) 记录该特征字符串对应实体标识的最大值。编写实体代码时,我们指定实体标识由数据库表中指定的特征字符串 ( 如 customID) 对应的列 SEQUENCE_VALUE处理,当有新的实体被持久化时,容器将获取行 customID、列 SEQUENCE_VALUE对应的数值 +1 后作为新实体的标识,同时将该列的值也自动 +1。
要将实体标识的自动生成委托给数据库表,需要数据库和实体定义的双方配合才能够达到:首先,必须在数据库中创建合适的保存实体标识的表,另外还需要为实体标识字段提供 TableGenerator注释,设置它的参数,为实体类提供关于数据库表、字段的信息,同时将实体类中实体标识字段的 GeneratedValue注释的 stragety属性的值设置为 GenerationType.Table,将 generator属性的值设置为 SequenceGenerator注释的 name属性的值。
我们以 Animal 实体类来说明使用数据库表自动生成实体标识所需要采取的步骤:我们假设存在这样的场景,Animal 实体的标识由应用程序中自定义的数据库表 MY_KEYS自动生成,MY_KEYS表中有两列,一列是 KEYID,它保存实体标识的特征值,一列是 KEYVALUE,它保存实体当前的最大编号,除此之外,我们还决定使用 ANIMALID作为 Animal 实体标识的特征字符串。
首先,在数据库中使用下面的 SQL 语句创建名为 MY_KEYS的数据库表。在 OpenJPA 容器中,如果我们没有创建 MY_KEYS表,OpenJPA 容器将帮我们自动生成对应的表结构。

清单 9. 创建数据库表 MY_KEYS

 CREATE TABLE MY_KEYS (     KEYID VARCHAR(255) NOT NULL,     KEYVALUE BIGINT,     PRIMARY KEY (KEYID)  ) 


在数据库部分创建合适的数据库表后,在实体 Animal 的定义中,我们需要将 id字段 GeneratedValue注释的 stragety属性的值设置为 GenerationType.TABLE,设置它的 generator属性的值为 TableGenerator。我们还需要为 id字段提供另外一个注释 TableGenerator,设置它的 name属性为 TableGenerator,设置它的 table属性为 MYKEYS、pkColumnName属性为 KEYID、valueColumnName属性为 KEYVALUE、 ANIMALID属性为 ANIMALID。Animal 实体类修改后的代码片段如下。

清单 10. 标识由数据库表生成的 Animal 实体类

import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;     @Entity public class Animal {     @Id     @GeneratedValue(strategy = GenerationType.TABLE,     generator = " TableGenerator ")    @TableGenerator(name = " TableGenerator", table = "MY_KEYS",    pkColumnName = "KEYID", valueColumnName = "KEYVALUE", pkColumnValue = "ANIMALID")    private long id;    private String name;            …      }



调用代码
上面的章节中我们学习了分别使用四种方式来自动生成实体的标识,由于这四种情况下,Animal 实体的标识都由 OpenJPA 和数据库协作后自动生成,对于开发者而言,这个过程是透明的,因此我们可以使用相同的方式来创建这些实体:创建新的 Animal 实例的时候不再需要为主键字段提供属性值,只需要设置 Animal 实例的非标识字段 name的值即可。下面的代码演示了 Animal 实例的持久化代码,请注意代码中并没有调用 Animal 实例的 setId 方法。

清单 11. Animal 实例的持久化代码

 EntityManagerFactory factory = Persistence.          createEntityManagerFactory(          "jpa-unit", System.getProperties());  EntityManager em = factory.createEntityManager();  em.getTransaction().begin();  Animal animal = new Animal(); // 此处不需要调用 animal 的 setId 方法 animal.setName("ba guai!"); em.persist(animal); em.getTransaction().commit();  em.close();  em2.close();  factory.close();



总结
本文介绍了开发者使用 OpenJPA 实现实体标识自动生成时可选择使用的注释,并且结合简单的例子,分别介绍了 OpenJPA 中实现容器管理的实体标识自动生成、结合数据库自增长字段、序列号、数据库表等特性实现实体标识自动生成时注释的具体用法和操作步骤。

读书人网 >软件架构设计

热点推荐