jpa 主建

将主键与 Java 持久性配合使用
作者:Sean Brydon 及 Smitha Kangath
状态:在 Early Access 版本阶段

问题描述
持久性实体需要主键。本文介绍了在开发使用 Java 持久性 API 的应用程序的模型层时使用主键的一些指南和提示。我们先看一下主键是如何定义的,然后介绍一些生成主键的策略。

解决方案
主键需要遵循一些指南。
简单或复合?
主键类型可以是 Java 基元类型(int、byte 和 long 等)、基元包装类型(Integer、Byte 和 Long 等)、java.lang.String、java.util.Date 或 java.sql.Date。除了选择类型外,应用程序开发者还必须选择是使用简单主键,还是使用复合主键。

简单主键涉及实体的单个持久性字段或属性。可以使用 @Id 标注来表示一个简单主键。使用 @Id 标注表示的实体字段(或属性)将映射到相应数据库表的主键上。下面的代码示例 1 说明了如何定义简单主键:

@Entity public class Item {
...
//use default column mapping
String itemId;

@Id
public String getItemId() {
return itemId;
}
...
}
代码示例 1:简单主键

复合主键可以对应单个也可以对应一组持久性字段,并且由主键类表示。复合键通常定义为包含几个表列的嵌入类。当相应数据库表的主键包含几个列时,复合键是非常有用的。复合键可以由 @EmbeddedId 或 @IdClass 标注进行定义。在使用 @EmbeddedId 标注时,表示复合键的类是由 @Embeddable 标注指定的嵌入类。请注意,复合键类必须是可序列化的,并且必须定义 equals 和 hashCode 方法。下面的代码示例 2 说明了如何使用 @EmbeddedId 标注来定义复合键。

@Embeddable public class ItemId implements Serializable {
private String firstName;
private String lastName;
...

}

@Entity public class Item implements Serializable {
private ItemId id;
@EmbeddedId public ItemId getId() {
return id;
}
...
}
代码示例 2:使用 @EmbeddedId 标注的复合主键

用于生成主键的策略
主键可以由数据库、容器或应用程序自身来生成。如果主键是由提供程序或数据库生成的,则称主键是自动生成的。
在应用程序中生成主键

此处的一个选择是,将数据固有的部分作为主键。在这种情况下,数据已包含主键,因此,不需要生成或指定主键,应用程序只需使用数据的主键即可。例如,表示美国纳税人的实体的社会保险号码。如果应用程序数据尚未包含主键,则在创建数据并将其存储到数据库中时,应用程序需要为数据指定主键。应用程序必须以编程方式创建和设置主键的值。一种完成此操作的方法是,创建一个 Id 表(类似于序列号)和一个映射到该表上的关联对象,并使用类似于 IdGenerator.getNextId() 的 API。另一种方法是只调用 System.currentTimeMillis()。切记,这些方法可能无法在群集环境中按预期方式工作。

让应用程序生成主键的一个优点是,您可以在数据库中永久保存数据之前了解该键。

自动生成主键

另一种选择是,自动生成 Id 并让提供程序或数据库设置该值。通过这种方式,应用程序开发者不需要编写任何 Id 生成逻辑。只需在 Id 上使用标注,然后把工作交给提供程序和数据库即可。

Java 持久性提供了一种以这种方式自动生成主键的机制(通过使用 @GeneratedValue 标注)。我们必须提供一种可选的 id 生成策略和可选的 id 生成器。@GeneratedValue 标注是与 @Id 标注结合使用的。

持久性提供程序可以使用多种生成策略来生成主键。生成策略可以是 TABLE、SEQUENCE、IDENTITY 或 AUTO 之一。

对于 IDENTITY 策略,数据库添加一个标识列并负责完成主键生成操作。此策略特定于数据库,并要求数据库支持 IDENTITY 列类型。因此,可能无法在数据库之间移植此策略。

对于 SEQUENCE 策略,持久性提供程序使用一个可以生成主键的数据库序列。此策略要求数据库支持 Sequence,因而可能无法在数据库之间进行移植。

TABLE 策略是指基于基本的数据库表来分配值。由于 TABLE 策略不基于任何特定于数据库的功能,因此,可以在数据库之间进行移植。

AUTO 是指持久性提供程序应该选取适当的策略。

SEQUENCE、IDENTITY 和 AUTO 策略不能在数据库之间进行移植。还要切记,AUTO、SEQUENCE 和 TABLE 策略可能无法在群集环境中正常工作。所有这三种策略都需要提供程序来完成某些操作。如果提供程序无法完成此类操作,则可能会导致数据不一致。在 IDENTITY 策略中,数据库不使用提供程序来完成操作,因而不会在群集环境中出现数据不一致。

被指定为一个 GeneratedValue 标注元素的 id 生成器必须用 SequenceGenerator 标注或 TableGenerator 标注进行声明,具体取决于生成策略是 SEQUENCE 还是 TABLE。可以在实体类或主键字段上声明这两种标注。生成器的范围是持久性单元。

让我们看一下,如何使用 TABLE 生成策略自动生成主键。通过使用表生成器,持久性提供程序使用数据库表来存储生成的 Id。一个实体将在表中单占一行。表具有主键名称列和值列。用于创建生成器表的 SQL 命令类似于:

CREATE TABLE ID_GEN(GEN_KEY VARCHAR(10) NOT NULL, GEN_VALUE INTEGER NOT NULL,primary key (GEN_KEY));

必须在此表中插入一行以作为生成器的种子。
INSERT INTO ID_GEN VALUES('ITEM_ID',101);

现在,假设有一个 Item 表,将项 id 用作主键,则生成的 Id 将从 102 开始。您必须在 Item 的持久性实体的 TableGenerator 标注中引用此表。该标注类似于:

@TableGenerator(name="ID_GEN",
table="ID_GEN",
pkColumnName="GEN_KEY",
valueColumnName="GEN_VALUE",
pkColumnValue="ITEM_ID",
allocationSize=1)
@GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GEN")
@Id
public int getItemID() {
return itemID;
}
...

一种常见的使用情况是,当前已有一个数据库,而需要为新插入的记录自动生成主键。如果现有主键是连续的类型,则可以实现此操作。

保留主键

所有实体必须有一个主键,这是第一次将实体永久保存到数据库时设置的。应用程序不应修改 Java 实体的主键值,也没有定义应用程序尝试修改主键值时的实际行为。如果应用程序确实需要修改主键,则应该先删除实体,然后使用新主键添加该实体,同时还要确保引用完整性。对于大多数情况,应用程序不需要修改主键。例如,使用自动生成的 id 时,应用程序甚至不需要设置它的值。由于实体包含不应该被应用程序代码修改的标识字段(或主键类),因此,在代码中设置一些保护措施以防止更改主键是非常有用的。一种方法是通过 Javadoc 对 setPK(...) 方法或类进行描述,向用户指明不要调用主键的 set 方法,以及不要直接访问主键字段以对其进行修改。然而,这不是一种自身起作用的方法。因为实体开发者总是希望使团队中的其他开发者能够方便、正确地使用该实体。例如,一个 Web 开发者在编写代码调用实体时,可能会无意中修改该实体的主键值。

请注意,除了访问实体字段和方法的应用程序代码外,容器和持久性提供程序也会访问实体。容器将通过字段或属性方法来访问实体,具体取决于应用程序代码所指定的方法。容器/提供程序必须访问主键值,因为它管理实体中的数据与数据库表中的数据之间的交互,并将它们保持同步以及执行其他操作。对于自动生成的 Id,容器还会访问实体,因为它将为主键分配值。因此,无论使用哪种机制来尝试并防止代码无意中修改主键,都必须考虑到容器需要访问实体的主键字段(或主键类)。

让我们简要介绍一种策略,它以一种使用户避免无意中修改主键的方式来开发实体 Bean。以下是实现此策略所涉及的五个步骤:

使用基于字段的标注。通过使用基于字段的标注,容器/提供程序将使用基于字段的访问来访问实体值。如果使用基于属性的访问,则必须为所有持久性字段(包括主键)公开 setter 和 getter 的 API,而此时您并不希望公开主键的 setter 方法。
在字段上使用包范围。这会降低主键字段对其他开发者的可见性,但容器仍然可以访问它们。持久性提供程序和容器应该能够访问它们,因此,不应将其设置为私有。
为其他字段提供 getter 和 setter 方法,和一个用于主键字段的 getter 方法。实体客户端可以访问这些方法。

制订一个策略,使开发者只能通过 getter 和 setter 方法来访问实体。

在主键的实体上设置一个私有的 setPK(...) 方法,使得只能在类内部访问它。这可以防止开发者在代码中使用实体时,调用 setPK(...) 方法并修改它的值。或者,干脆不为主键提供 setter 方法。
参考资料
Java 持久性 API 的 Javadoc
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值