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

JPA通过公共列关联多表时出现插入异常的问题咨询

问题排查与解决

插入报错的根源:

  1. 直接把关联实体Project和DomainUser标记为@Id,但@IdClass对应的必须是基本类型的主键字段,不能是关联对象。
  2. 所有关联列都设置了insertable = false, updatable = false,导致插入时JPA不会往project_id、domain_id、user_account_token这些主键字段写入值,数据库因主键非空约束报错。
修复步骤

1. 修正主键类ProjectUserId

必须让它包含project_users表的所有主键字段,且实现Serializable接口,重写equals和hashCode方法:

import lombok.Data;
import java.io.Serializable;
import java.util.Objects;

@Data
public class ProjectUserId implements Serializable {
    private Long projectId; // 对应project表的id
    private String userAccountToken; // 对应domain_user表的user_account_token_id
    private Long domainId; // 共用的domain_id

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ProjectUserId that = (ProjectUserId) o;
        return Objects.equals(projectId, that.projectId) &&
                Objects.equals(userAccountToken, that.userAccountToken) &&
                Objects.equals(domainId, that.domainId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(projectId, userAccountToken, domainId);
    }
}

2. 重构ProjectUser实体类

先直接定义主键字段,再通过这些字段关联Project和DomainUser,关联列保留insertable=false, updatable=false以避免重复操作:

@Entity
@Data
@Table(name = "project_users", schema = "public")
@IdClass(ProjectUserId.class)
public class ProjectUser implements Persistable<ProjectUserId> {
    @Transient
    private boolean isNew = true;

    // 直接定义主键字段,插入时手动赋值
    @Id
    @Column(name = "project_id")
    private Long projectId;

    @Id
    @Column(name = "user_account_token")
    private String userAccountToken;

    @Id
    @Column(name = "domain_id")
    private Long domainId;

    // 关联Project表,关联列设为不可插入/更新
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(name = "project_id", referencedColumnName = "id", insertable = false, updatable = false),
            @JoinColumn(name = "domain_id", referencedColumnName = "domain_id", insertable = false, updatable = false)
    })
    private Project project;

    // 关联DomainUser表,同理
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(name = "domain_id", referencedColumnName = "domain_id", insertable = false, updatable = false),
            @JoinColumn(name = "user_account_token", referencedColumnName = "user_account_token_id", insertable = false, updatable = false)
    })
    private DomainUser domainUser;

    // 业务字段示例
    @Column(name = "last_access")
    private LocalDateTime lastAccess;

    @Column(name = "ref_project_user_role_id")
    private Long roleId;

    @Override
    public ProjectUserId getId() {
        ProjectUserId id = new ProjectUserId();
        id.setProjectId(projectId);
        id.setUserAccountToken(userAccountToken);
        id.setDomainId(domainId);
        return id;
    }

    @Override
    public boolean isNew() {
        return isNew;
    }
}

3. 插入操作的正确方式

插入时直接给主键字段赋值即可,关联实体无需手动设置,JPA会通过主键自动关联:

ProjectUser projectUser = new ProjectUser();
projectUser.setProjectId(1L); // 使用已存在的project的id
projectUser.setDomainId(100L); // 对应公共的domain_id
projectUser.setUserAccountToken("user_token_123"); // 使用已存在的domain_user的token
projectUser.setLastAccess(LocalDateTime.now());
projectUser.setRoleId(2L);

projectUserRepository.save(projectUser);

关键说明

  • JPA的@IdClass要求主键是基本类型组合,不能用关联实体作为主键,否则框架无法正确解析主键值。
  • 单独定义主键字段并手动赋值,才能让数据库获取合法的主键值,避免非空报错。
  • 关联列设置不可插入/更新,是为了防止同一字段被主键和关联实体重复修改,保证数据一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:35:19