JPA通过公共列关联多表时出现插入异常的问题咨询
问题排查与解决
插入报错的根源:
- 直接把关联实体
Project和DomainUser标记为@Id,但@IdClass对应的必须是基本类型的主键字段,不能是关联对象。 - 所有关联列都设置了
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
相关产品推荐
相关产品推荐

