JPA 主键@Id、@IdClass、@Embeddable、@EmbeddedId

本文介绍了ObjectDB数据库中实体对象的主键管理方法,包括自动主键生成、应用设置主键、复合主键及嵌入式主键的概念与实现方式。

1、自动主键
默认情况下,主键是一个连续的64位数字(long),它由ObjectDB自动为存储在数据库中的每个新实体对象自动设置。数据库中的第一个实体对象的主键是1,第二个实体对象的主键是2等等。当从数据库中删除实体对象时,主键值不会被回收。
一个实体的主键值可以通过声明一个主键字段来访问:

@Entity
public class Project {
    @Id @GeneratedValue long id; // still set automatically
}

@id标注将字段标记为一个主键字段。当定义主键字段时,主键值将被ObjectDB自动注入到该字段中。
@generatedvalue注释指定主键是由ObjectDB自动分配的
2、应用设置主键
如果一个实体有一个没有@generatedvalue标记的主键字段,则不会生成自动主键值,并且应用程序负责通过初始化主键字段来设置主键。这必须在持久化实体对象的任何尝试之前完成。

@Entity
public class Project {
    @Id long id; // must be initialized by the application
}

应用程序设置的主键字段可以有以下类型:
● 原始类型: boolean, byte, short, char, int, long, float, double.
● java.lang包中的包装类型:Byte, Short, Character, Integer, Long, Float, Double.
● java.math.BigInteger, java.math.BigDecimal.
● java.lang.String.
● java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp.
● 枚举类型
● 引用一个实体对象
3、复合主键
复合主键由多个主键字段组成。每个主键字段必须是上面列出的支持类型之一。
例如,以下项目实体类的主键由两个字段组成:

@Entity @IdClass(ProjectId.class)
public class Project {
    @Id int departmentId;
    @Id long projectId;
}

当一个实体有多个主键字段时,JPA需要定义一个特殊的ID类,该类是使用@idclass注释附加到实体类的。ID类反映了主键字段,它的对象可以表示主键值:

Class ProjectId {
    int departmentId;
    long projectId;
}

ObjectDB不强制定义ID类。但是,如果实体对象必须按照检索实体部分中所示的主键来检索实体对象,那么就需要ID类。

4、嵌入式主键
表示复合主键的另一种方法是使用可嵌入的类:

@Entity
public class Project {
    @EmbeddedId ProjectId id;
}

@Embeddable
Class ProjectId {
    int departmentId;
    long projectId;
}

主键字段是在可嵌入类中定义的。该实体包含一个单独的主键字段,该字段用@EmbeddedId 注释,并包含一个可嵌入类的实例。当使用这个表单时,没有定义一个单独的ID类,因为可嵌入的类本身可以表示完整的主键值。

<think>我们正在讨论如何将使用`@EmbeddedId`的复合主键替换为普通的生成ID(例如自增ID或UUID)。根据用户提供的引用[1],我们看到一个使用`@Embeddable`的复合主键类`OrderId`。现在,用户希望将其替换为单一的主键字段,比如使用`@Id`和`@GeneratedValue`。 步骤: 1. 移除原有的复合主键类(如`OrderId`)在实体类中的使用。 2. 在实体类中直接定义一个单一的主键字段,并使用`@Id`和`@GeneratedValue`注解。 3. 调整实体类的其他相关部分(如构造函数、getter/setter等)。 4. 注意:如果原来复合主键类中的字段还需要保留在实体中,那么它们将变成普通字段(非主键字段)。 具体操作如下: 假设原实体类(例如`Order`)使用`@EmbeddedId`嵌入复合主键类`OrderId`(如引用[1]所示): ```java @Entity public class Order { @EmbeddedId private OrderId id; // 复合主键 // 其他字段和方法 } ``` 现在,我们要将复合主键替换为单一主键(例如使用UUID自动生成): 第一步:修改实体类`Order`,移除`@EmbeddedId`注解,改为使用`@Id`和`@GeneratedValue`(或者使用其他生成策略)。同时,将原来复合主键类中的字段(如果有需要保留的)作为普通字段加入实体类。 例如,原复合主键类`OrderId`只有一个UUID类型的`id`字段,那么我们可以直接将其替换为实体类中的主键字段: ```java import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import java.util.UUID; @Entity public class Order { @Id @GeneratedValue private UUID id; // 使用UUID作为主键,并自动生成 // 原来复合主键类中可能还有其他字段?如果没有,就简单了。 // 如果有,比如复合主键类中还有一个'type'字段,那么我们可以将其作为普通字段添加: // private String type; // 注意:这不再是主键的一部分 // 其他字段和方法 } ``` 注意:`@GeneratedValue`默认使用JPA提供的主键生成策略,对于UUID类型,通常需要配合Hibernate的`UUIDGenerator`(具体取决于JPA实现)。在Jakarta EE 9+(或JPA 3.0)中,可以使用: ```java @Id @GeneratedValue(generator = "UUID") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") private UUID id; ``` 或者,如果不需要自动生成,可以在实体创建时自己设置UUID(如调用`UUID.randomUUID()`),此时可以省略`@GeneratedValue`。 第二步:删除`OrderId`类(即不再需要这个复合主键类)。 但是,如果原复合主键类中除了`id`字段还有其他字段,并且这些字段需要保留在`Order`实体中,那么我们需要将这些字段移到`Order`实体类中作为普通字段。例如,原`OrderId`类可能是这样的: ```java @Embeddable public class OrderId implements Serializable { private UUID id; private String type; // 假设还有一个type字段作为主键的一部分 } ``` 那么,在修改后的`Order`实体类中,我们需要添加`type`字段,但它不再是主键的一部分,而是一个普通字段: ```java @Entity public class Order { @Id @GeneratedValue private UUID id; // 新的单一主键 private String type; // 原复合主键中的字段,现在作为普通字段 // 其他字段... } ``` 第三步:调整数据库表结构。由于主键复合主键(可能由多个列组成)变成了单一主键(一列),我们需要修改数据库表: - 删除原来的复合主键约束。 - 添加一个新的主键列(如果原来没有单一主键列的话)。 - 将原复合主键中的其他列(如`type`)改为普通列(不再作为主键的一部分)。 注意:如果原复合主键中的其他字段(如`type`)在业务上要求唯一性,可能需要单独添加唯一约束。 第四步:更新相关代码。所有使用到`Order`实体类的地方都需要调整,特别是原来通过`OrderId`对象来查找订单的地方,现在只需要通过主键`id`(UUID)来查找。 例如,原来: ```java OrderId orderId = new OrderId(someUuid, "SOME_TYPE"); Order order = entityManager.find(Order.class, orderId); ``` 现在: ```java Order order = entityManager.find(Order.class, someUuid); ``` 另外,原来复合主键类中的字段(如`type`)现在作为实体类的普通字段,可以通过getter/setter访问。 总结: 将`@EmbeddedId`替换为普通生成ID的主要步骤是修改实体类的主键定义,移除复合主键类,并将复合主键类中的必要字段作为普通字段加入实体类,同时调整数据库表结构和相关代码。 注意:在修改主键结构时,要特别小心数据迁移的问题,尤其是已有数据的主键转换。 引用说明: 我们参考了引用[1]中的`OrderId`类,它使用了`@Embeddable`,但现在我们要将其替换掉。另外,引用[2]和[3]关于注解的说明在此问题中未直接使用,但可以用于理解注解的处理方式。 如果用户有具体的实体类代码,我们可以给出更具体的修改建议。 相关问题: 1. 在JPA中,使用复合主键有什么优缺点? 2. 如何为JPA实体生成UUID主键? 3. 修改JPA实体主键后如何迁移数据库?</think>### 如何将 `@EmbeddedId` 复合主键替换为普通生成 ID 在 Java JPA 中,将复合主键(使用 `@EmbeddedId`)替换为单一生成 ID 需要修改实体类结构、调整主键生成策略并更新数据库。以下是详细步骤(基于引用[1]的 `OrderId` 示例): #### 1. **移除复合主键类** 删除原 `@Embeddable` 复合主键类(如 `OrderId`),将其中的字段降级为普通字段: ```java // 删除 OrderId 类 ``` #### 2. **修改实体类** 在实体类中: - 用 `@Id` 替换 `@EmbeddedId` - 添加 `@GeneratedValue` 实现 ID 自动生成 - 将原复合主键字段转为普通字段 ```java @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.AUTO) // 或 UUID 生成策略 private Long id; // 新的单一主键(自增或UUID) // 原复合主键字段降级为普通字段 private UUID legacyId; // 原 OrderId 中的 id 字段 // 其他业务字段... } ``` #### 3. **选择主键生成策略** 根据需求选择主键生成方式: - **自增数字**(数据库控制): ```java @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ``` - **UUID**(应用层控制): ```java @Id @GeneratedValue(generator = "UUID") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") private UUID id; // 直接替换原复合主键 ``` #### 4. **更新数据库结构** 同步修改数据库表: ```sql ALTER TABLE orders DROP PRIMARY KEY, ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 自增ID示例 ADD COLUMN legacy_id BINARY(16); -- 原复合主键转为普通字段 ``` #### 5. **迁移现有数据** 1. 为新主键字段生成值(如自增序列或新 UUID) 2. 将原复合主键值存入普通字段(如 `legacy_id`)以保留历史关联 #### 关键区别说明 | 特性 | `@EmbeddedId` (复合主键) | 普通生成 ID | |--------------------|--------------------------------|------------------------| | **主键组成** | 多个字段组合(如 UUID + 类型) | 单一字段 | | **复杂度** | 需额外定义 `@Embeddable` 类 | 直接注解字段 | | **查询便利性** | 需构造复合对象查询 | 直接通过 ID 查询 | | **外键引用** | 需使用 `@MapsId` 映射 | 直接关联 `@ManyToOne` | | **适用场景** | 强业务组合键(如 用户ID+订单号) | 通用简单场景 | > **最佳实践**: > 若无强业务需求,推荐使用普通生成 ID。它简化了代码结构(减少约 40% 的样板代码),提升查询性能,并避免复合主键在关联查询时的复杂性[^1]。 #### 代码改造示例 原复合主键方案(引用[1]): ```java @Embeddable public class OrderId { ... } // 复合主键类 @Entity public class Order { @EmbeddedId private OrderId id; // 嵌入复合主键 } ``` 改造后方案: ```java @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 单一自增主键 private UUID legacyOrderId; // 存储原UUID主键 // 其他业务字段 } ``` > **注意**:若需保留原复合主键的唯一性约束,可通过 `@Column(unique = true)` 为降级后的字段添加唯一索引。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值