数据的唯一性是所有应用程序非常基本的要求,由开发者或者用户来维护这种唯一性存在着较大的风险,因此,由系统自动产生唯一标识是一种常见的做法。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 注释属性说明
属性 | 说明 |
---|---|
name | 该属性是必须设置的属性,它表示了 SequenceGenerator 注释在 OpenJPA 容器中的唯一名称,将会被 GeneratedValue 注释的 generator 属性使用。将实体标识的自动生成委托给数据库的序列号特性时,实体标识字段的 GeneratedValue 注释的 generator 属性的值必须和某个SequenceGenerator 注释的 name 属性值保持一致。 |
sequenceName | 实体标识所使用的数据库序列号的名称。该属性是可选的,如果我们没有为该属性设置值,OpenJPA 框架将自动创建名为 OPENJPA_SEQUENCE 的序列号。如果一个 OpenJPA 容器中管理的多个实体都选择使用序列号机制生成实体标识,而且实体类中都没有指定标识字段的 sequenceName 属性,那么这些实体将会共享系统提供的默认名为 OPENJPA_SEQUENCE 的序列号。这可能引起实体类编号的不连续。我们可以用下面的这个简单例子说明这种情况:假设 OpenJPA 容器中存在两个实体类 Dog 和 Fish,它们的实体标识字段都是数值型,并且都选择使用序列号生成实体标识,但是实体类中并没有提供 sequenceName 属性值。当我们首先持久化一个 Dog 对象时,它的实体标识将会是 1,紧接着我们持久化一个 Fish 对象,它的实体标识就是 2,依次类推。 |
initialValue | 该属性设置所使用序列号的起始值。 |
allocationSize | 一些数据库的序列化机制允许预先分配序列号,比如 Oracle,这种预先分配机制可以一次性生成多个序列号,然后放在 cache 中,数据库用户获取的序列号是从序列号 cache 中获取的,这样就避免了在每一次数据库用户获取序列号的时候都要重新生成序列号。allocationSize 属性设置的就是一次预先分配序列号的数目,默认情况下allocationSize 属性的值是 50。 |
@javax.persistence.TableGenerator
如果实体标识的自动生策略是 GenerationType.TABLE,开发者需要为实体标识字段提供 TableGenerator 注释,它的参数描述了使用数据库表生成实体标识的具体细节。该注释支持下列属性:
表 2. TableGenerator 注释属性说明
属性 | 说明 |
---|---|
name | 该属性是必须设置的属性,它表示了 TableGenerator 注释在 OpenJPA 容器中的唯一名称,将会被 GeneratedValue 注释的 generator 属性所使用。将实体标识的自动生成委托给数据库表时,实体标识字段的 GeneratedValue 注释的 generator 属性的值必须和某个 TableGenerator 注释的 name 属性值保持一致。 |
table | 该属性设置的是生成序列号的表的名称。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认的表名 OPENJPA_SEQUENCES_TABLE 。 |
schema | 该属性设置的是生成序列号的表的 schema。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会默认使用当前数据库用户对应的 schema。 |
catalog | 该属性设置的是生成序列号的表的 catalog。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认当前数据库用户对应的 catalog。 |
pkColumnName | 该属性设置的是生成序列号的表中的主键字段的名称,该字段将保存代表每个实体对应的标识值对应的特征字符串。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认值 ID 。 |
valueColumnName | 该属性设置的是生成序列号的表中记录实体对应标识最大值的字段的名称。该属性并不是必须设置的属性,如果开发者没有为该 属性设置值,OpenJPA 容器将会使用默认值SEQUENCE_VALUE 。 |
pkColumnValue | 该属性设置的是生成序列号的表中的主键字段的特征字符串值 ( 比如 customID ),该字段将保存代表每个实体对应的标识值对应的特征字符串。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认值 DEFAULT 。可以为多个实体设置相同的 pkColumnValue 属性值,这些实体标识的生成将通过同一列的值的递增来实现。 |
initialValue | 该属性设置的是生成序列号的表实体标识的初始值。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认值 0 。 |
allocationSize | 为了降低标识生成时频繁操作数据库造成 的性能上的影响,实体标识生成的时候会一次性的获取多个实体标识,该属性设置的就是一次性获取实体标识的数目。该属性并不是必须设置的属性,如果开发者没有为该属性设置值,OpenJPA 容器将会使用默认值 50 。 |
实体标识自动生成
在上面的小节中,我们了解了和实体标识自动生成相关的注释,接下来我们将结合一个简单的例子讲述如何分别使用这些实体标识自动生成策略实现实体标识的自动生成。
我们首先假设有一个 Animal
实体需要被持久化,它包括 ID
和 NAME
属性,其中 ID
是它的主键字段。Animal
实体的标识需要自动生成,我们将分析在这四种不用的情况下,如何使用 OpenJPA 提供的注释,结合具体数据库支持的特性,如自增长字段、序列号等来实现实体标识的自动生成。
OpenJPA 容器默认的实体标识自动生成策略是由容器管理实体标识的自动生成,容器管理的实体标识可以支持数值型和字符型两种。当容器管理的实体标识是数字型时,OpenJPA 容器自动创建一个数据库表 OPENJPA_SEQUENCE_TABLE
,用其中的SEQUENCE_VALUE
字段来记录实体的实体标识的增长。
当容器管理的实体标识是字符串类型时,OpenJPA 支持使用 uuid-string 和 uuid-hex 两种方式生成相应的实体标识。如果我们选择使用 uuid-string 方式生成实体标识时,OpenJPA 框架会自动为实体生成一个 128 位的 UUID,并且将这个 UUID 转化为使用 16 位字符表示的字符串。如果我们选择使用 uuid-hex 方式生成实体标识时,OpenJPA 框架会自动为实体生成一个 128 位的 UUID,并且将这个 UUID 转化为使用 32 位字符表示的 16 进制的字符串。
数值标识
容器管理的实体标识可以是数值型的,OpenJPA 框架管理的实体标识借助于数据库的表来实现,在运行时 OpenJPA 框架会自动在数据库中创建表 OPENJPA_SEQUENCE_TABLE
。它有两个字段:ID
和 SEQUENCE_VALUE
,这两个字段都是数值类型,其中 ID
是表的主键字段,它的内容是查询当前实体标识时所使用的关键词,默认值是 0。而 SEQUENCE_VALUE
记录了当前 OpenJPA 框架中当前实体标识的历史数据,内容是已经被获取实体标识的最大数值加 1。
我们要使用注释描述 Animal
实体的标识由容器自动生成,只需要为它的标识字段提供 GeneratedValue
注释,并且把它的 strategy
属性设置为 GenerationType.AUTO
, Animal
实体类的代码片断如下:
清单 1. 标识由容器自动生成的 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.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 中实现容器管理的实体标识自动生成、结合数据库自增长字段、序列号、数据库表等特性实现实体标识自动生成时注释的具体用法和操作步骤。