You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring R2DBC多对多关系复合主键配置及更新问题

Spring R2DBC 复合主键配置与更新实现

问题描述

我有一个处理student和tutor表多对多关系的实体,对应数据库表tutor_student,主键为复合键(tutor_id和student_id),实体代码如下:

@Getter
@Setter
@NoArgsConstructor
@Table("tutor_student")
public class TutorStudentEntity implements Serializable {

    @Column("tutor_id")
    private Long tutorId;
    @Column("student_id")
    private String studentId;
    @Column("status")
    private String status;

}

使用ReactiveCrudRepository#save方法更新记录时,Spring R2DBC因无法识别主键而尝试创建新记录。JPA中的@EmbeddedId和@IdClass方案不适用于Spring R2DBC,需要找到适配的配置方式。

解决方案

Spring R2DBC通过@Id注解识别主键,复合主键可通过以下两种方式配置,让save方法自动区分插入与更新:

1. 方式一:直接标注多个主键字段

在实体类的复合主键字段上分别添加@Id注解,Spring R2DBC会自动识别这是复合主键:

@Getter
@Setter
@NoArgsConstructor
@Table("tutor_student")
public class TutorStudentEntity implements Serializable {

    @Id
    @Column("tutor_id")
    private Long tutorId;

    @Id
    @Column("student_id")
    private String studentId;

    @Column("status")
    private String status;
}

对应的Repository接口需要使用Tuple2作为主键类型:

public interface TutorStudentRepository extends ReactiveCrudRepository<TutorStudentEntity, Tuple2<Long, String>> {
}

2. 方式二:使用自定义复合主键类

这种方式更易维护,适合复杂的复合主键场景:

步骤1:创建复合主键类

定义实现Serializable的主键类,并重写equals和hashCode方法(用于Spring判断记录唯一性):

@Getter
@Setter
@EqualsAndHashCode // Lombok注解自动生成equals和hashCode
public class TutorStudentId implements Serializable {
    private Long tutorId;
    private String studentId;
}

步骤2:修改实体类关联主键

将主键类作为实体字段,并用@Id注解标注:

@Getter
@Setter
@NoArgsConstructor
@Table("tutor_student")
public class TutorStudentEntity implements Serializable {

    @Id
    private TutorStudentId id;

    @Column("status")
    private String status;

    // 可选:添加单独字段的getter/setter,方便业务代码调用
    public Long getTutorId() {
        return id != null ? id.getTutorId() : null;
    }

    public void setTutorId(Long tutorId) {
        if (id == null) {
            id = new TutorStudentId();
        }
        id.setTutorId(tutorId);
    }

    public String getStudentId() {
        return id != null ? id.getStudentId() : null;
    }

    public void setStudentId(String studentId) {
        if (id == null) {
            id = new TutorStudentId();
        }
        id.setStudentId(studentId);
    }
}

步骤3:定义Repository接口

将自定义主键类作为Repository的主键泛型:

public interface TutorStudentRepository extends ReactiveCrudRepository<TutorStudentEntity, TutorStudentId> {
}

验证更新逻辑

配置完成后,调用save方法时,Spring R2DBC会根据复合主键判断:

  • 若主键对应的记录已存在,执行UPDATE操作
  • 若不存在,执行INSERT操作

示例代码:

// 构造需要更新的实体
TutorStudentEntity entity = new TutorStudentEntity();
entity.setTutorId(1L);
entity.setStudentId("S001");
entity.setStatus("INACTIVE");

// 调用save,自动执行更新
tutorStudentRepository.save(entity).subscribe();

关键注意事项

  • 复合主键类必须实现Serializable,这是Spring Data的强制要求
  • 必须正确重写equals和hashCode,否则Spring无法准确识别重复记录
  • 两种方式都能实现需求,推荐使用自定义主键类的方式,代码可读性和扩展性更好

内容的提问来源于stack exchange,提问作者Andrii Torzhkov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:12:53