实体标识的自动生成
转自: 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; …} CREATE TEXT TABLE ANIMAL ( ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY, NAME VARCHAR(255) NOT NULL )
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; …} CREATE SEQUENCE HELLOWORLDSEQUENCE START WITH 0 INCREMENT BY 1 MINVALUE 1 CACHE 50 NOCYCLE NOORDER
( ID CHAR(10), NAME VARCHAR2(100) NOT NULL, CONSTRAINT PK_ANIMAL PRIMARY KEY (ID ) )
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; … } CREATE TABLE MY_KEYS ( KEYID VARCHAR(255) NOT NULL, KEYVALUE BIGINT, PRIMARY KEY (KEYID) )
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; … } 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();