学习springbootjap中所遇到的问题;@MapsId,@EmbeddedId,@Embeddable,@IdClass的使用

由于之前较多接触的是mybatis,当刚开始做springbootjpa项目是还是有些许困惑的;

@MapsId

官方的解释是

Designates a <code>ManyToOne</code> or
<code>OneToOne</code> relationship attribute that provides the
mapping for an {@link EmbeddedId} primary key, an attribute within
an <code>EmbeddedId</code> primary key, or a simple primary key of
the parent entity. The <code>value</code> element specifies the
attribute within a composite key to which the relationship
attribute corresponds. If the entity's primary key is of the same
Java type as the primary key of the entity referenced by the
relationship, the value attribute is not specified.

大概意思是:映射EmbeddedId主键中的一个属性,或者是父实体的普通主键,value元素指定复合主键的属性对应关系,如果实体的主键与关系引用的实体的主键具有相同的Java类型,则不指定value属性。(意思很难懂)

经过摸索,得到一些demo,如下

//包换复合主键的实体
@Entity
public class ClassStudent implements Serializable {

    @EmbeddedId
    private ClassStudentId id;//复合主键

    private String msg;

    @MapsId("studentId")
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "student_id")
    private Student student;

    ...geter and setter
}
//可嵌入的类  在本例子中表示复合主键
@Embeddable//表示可内嵌
public class ClassStudentId implements Serializable {

    private Integer studentId;

    private Integer classId;

   //geter and setter

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ClassStudentId that = (ClassStudentId) o;
        return Objects.equals(studentId, that.studentId) &&
                Objects.equals(classId, that.classId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, classId);
    }
}
//主题一对一对应的类
@Entity
public class Student implements Serializable {
    @Id
    @Column(name = "student_id",
            columnDefinition = "INT(11) UNSIGNED NOT NULL COMMENT '学生id'"
    )
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "student_name",
            columnDefinition = "VARCHAR(255) COMMENT '学生姓名'"
    )
    private String name;
    @Column(name = "student_age",
            columnDefinition = "INT(11) UNSIGNED COMMENT '学生年龄'"
    )
    private Integer age;

    ...getter and setter
}

之前已经写出来了,查询没有问题,但是在保存的时候总是报错,大概是 assign id from null from property [student],表示需要相应的对应student,这是本人以为数据库中是不存在该字段的,所以就没有去管student,但是它们确实存在对应关系,保存时应该把student查询出来进行保存.具体代码如下:

@PostMapping
    public Object add(@RequestParam Integer studentId,@RequestParam Integer classId ){
        ClassStudent classStudent = new ClassStudent();
        Optional<Student> byId = studentRepository.findById(studentId);//报错时缺少的代码
        if(byId.isPresent()){
            ClassStudentId id = new ClassStudentId();
            id.setStudentId(studentId);//报错时缺少的代码
            id.setClassId(classId);
            classStudent.setId(id);
            classStudent.setMsg("走读");
            classStudent.setStudent(byId.get());
        }

@EmbeddedId和@IdClass

@EmbeddedId的用法上面已经展示,与之对应的有@IdClass,当使用idclass时,实体中须有和idclass类中属性个数的主键,并且一致.下边展示代码:

//作为主键id
@Data
public class StreamerSpecialityId implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 播主ID
     **/
    private Integer streamerId;

    /**
     * 播主服务ID
     */
    private Long specialityId;
}
//实体类
@Entity
@Table(name = "pls_service")
@IdClass(value = StreamerSpecialityId.class)
@Data
@EqualsAndHashCode(callSuper = true)
public class StreamerSpecialityEntity extends BaseEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 播主ID
     */
    @Id
    @Column(
            name = "streamer_id",
            columnDefinition = "INT(11) UNSIGNED NOT NULL COMMENT '播主ID'"
    )
    private Integer streamerId = 0;

    /**
     * 专长ID
     */
    @Id
    @Column(
            name = "speciality_id",
            columnDefinition = "INT(11) UNSIGNED NOT NULL COMMENT '专长ID'"
    )
    private Long specialityId = 0L;

    /**
     * 专长
     */
    @MapsId("specialityId")//对应复合主键的其中一个
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "speciality_id")
    private SpecialityEntity speciality;
}
@Entity
@Table(name = "pls_speciality")
@Data
@EqualsAndHashCode(callSuper = true)
public class SpecialityEntity extends BaseEntity {

    private static final long serialVersionUID = 1L;

    /**
     * ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 播主ID
     */
    @Column(
            name = "streamer_id",
            columnDefinition = "INT(11) UNSIGNED NOT NULL COMMENT '播主ID'"
    )
    private Integer streamerId = 0;

    /**
     * 服务名称
     */
    @Column(
            name = "name",
            columnDefinition = "VARCHAR(32) NOT NULL COMMENT '服务名称'"
    )
    private String name = "";

    /**
     * 是否内置
     */
    @Column(
            name = "is_buildin",
            columnDefinition = "TINYINT(1) NOT NULL COMMENT '是否内置'"
    )
    private Boolean buildin = Boolean.FALSE;
}

保存和修改时需要报关联的实体设置上(对应关系是内联的),不然就走不通

<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)` 为降级后的字段添加唯一索引。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值