Spring Data JPA一对多关联中间表保存报错求助
尝试通过Spring Data JPA实现User、Organization、Membership三个实体的关联关系,其中Membership是基于userId和organizationId复合ID的关联中间表,但保存该表维护关联关系时遇到错误:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: Could not set value of type [java.lang.Integer] : com.reeftrader.Hades.OrganizationMembership.OrganizationMembershipId.organizationId` (setter)] with root cause
java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "o" is null
以下是相关代码:
User实体
package com.reeftrader.Hades.user; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column( name = "user_id", updatable = false ) private int userId; @Column( name = "first_name", nullable = false, columnDefinition = "varchar(100)" ) @OneToMany(mappedBy = "user") private List<OrganizationMembership> membership; @OneToOne(cascade = CascadeType.ALL) @JoinTable(name = "user_identity", joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "identity_id", referencedColumnName = "identity_id") }) private Identity identity;
Organization实体
public class Organization { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column( name = "organization_id", updatable = false ) private int organizationId; @OneToMany(mappedBy = "organization", cascade = CascadeType.PERSIST) private List<OrganizationMembership> membership; @OneToOne(cascade = CascadeType.ALL) @JoinTable(name = "organization_identity", joinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "organization_id") }, inverseJoinColumns = { @JoinColumn(name = "identity_id", referencedColumnName = "identity_id") }) private Identity identity; }
Membership实体
public class OrganizationMembership { @EmbeddedId OrganizationMembershipId organizationMembershipId; @ManyToOne @MapsId("organization_id")//Name comes from OrganizationMembershipId attribute @JoinColumn(name = "organization_id") private Organization organization; @ManyToOne @MapsId("user_id")//Name comes from OrganizationMembershipId attribute @JoinColumn(name = "user_id") private User user; @Column(name = "membership_role") @NonNull private MembershipRole membershipRole; }
OrganizationMembershipID
@Data @Embeddable public class OrganizationMembershipId implements Serializable { @Column(name = "organization_id") private int organizationId; @Column(name = "user_id") private int userId; }
服务层保存方法
public Organization create(OrganizationRequest request) { List<OrganizationMembership> membershipList = new ArrayList<>(); Organization organization = Organization.builder() .organizationName(request.getOrganizationName()) .details(request.getDetails()) .establishedDate(request.getEstablishedDate()) .organizationType(request.getOrganizationType()) .membership(membershipList) .build(); // 构建要添加到成员关系的用户 User user = User.builder() .userId(request.getUserId()) .build(); OrganizationMembership membership = OrganizationMembership.builder() .user(user) .organization(organization) .membershipRole(MembershipRole.OWNER) .build(); organization.getMembership().add(membership); organizationRepository.save(organization); // 为Organization创建新的Identity Identity identity = Identity.builder() .organizationId(organization.getOrganizationId()) .identityType(IdentityType.ORGANIZATION) .build(); identityRepository.save(identity); return organization; }
Identity Repository
package com.reeftrader.Hades.Identity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface IdentityRepository extends JpaRepository<Identity, Integer> { List<Identity> findByIdentityType(IdentityType identityType); }
Organization Repository
package com.reeftrader.Hades.organization; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface OrganizationRepository extends JpaRepository<Organization, Integer> { List<Organization> findByOrganizationType(OrganizationType organizationType); }
疑问:如何正确使用Spring Data JPA向该中间表保存数据以维护实体间的关联关系?
1. 修复@MapsId属性名称不匹配问题
@MapsId需要匹配嵌入ID类中的属性名(而非数据库列名),当前代码中OrganizationMembership的@MapsId指定的organization_id、user_id与OrganizationMembershipId的驼峰属性名organizationId、userId不匹配,修改如下:
@ManyToOne @MapsId("organizationId") // 匹配嵌入ID类的organizationId属性 @JoinColumn(name = "organization_id") private Organization organization; @ManyToOne @MapsId("userId") // 匹配嵌入ID类的userId属性 @JoinColumn(name = "user_id") private User user;
2. 初始化嵌入ID对象
OrganizationMembership的organizationMembershipId字段未初始化是导致空指针异常的直接原因,构建Membership时必须显式初始化该对象:
OrganizationMembership membership = OrganizationMembership.builder() .user(user) .organization(organization) .membershipRole(MembershipRole.OWNER) .organizationMembershipId(new OrganizationMembershipId()) // 显式初始化嵌入ID .build();
3. 使用持久化状态的User对象
当前通过User.builder().userId(request.getUserId()).build()创建的是瞬时对象,JPA无法正确处理关联的瞬时对象,需从数据库加载持久化的User实例:
// 替换原User构建代码 User user = userRepository.findById(request.getUserId()) .orElseThrow(() -> new IllegalArgumentException("指定用户不存在"));
4. 调整保存逻辑,利用级联操作优化流程
由于Organization的@OneToMany配置了cascade = CascadeType.PERSIST,保存Organization时会自动级联保存关联的Membership,调整后的完整保存方法如下:
public Organization create(OrganizationRequest request) { // 1. 获取持久化用户 User user = userRepository.findById(request.getUserId()) .orElseThrow(() -> new IllegalArgumentException("指定用户不存在")); // 2. 构建Organization Organization organization = Organization.builder() .organizationName(request.getOrganizationName()) .details(request.getDetails()) .establishedDate(request.getEstablishedDate()) .organizationType(request.getOrganizationType()) .membership(new ArrayList<>()) .build(); // 3. 构建Membership并初始化嵌入ID OrganizationMembership membership = OrganizationMembership.builder() .user(user) .organization(organization) .membershipRole(MembershipRole.OWNER) .organizationMembershipId(new OrganizationMembershipId()) .build(); organization.getMembership().add(membership); // 4. 保存Organization,级联保存Membership Organization savedOrg = organizationRepository.save(organization); // 5. 构建并保存Identity Identity identity = Identity.builder() .organizationId(savedOrg.getOrganizationId()) .identityType(IdentityType.ORGANIZATION) .build(); identityRepository.save(identity); return savedOrg; }
5. 可选:自动同步嵌入ID(简化代码)
在OrganizationMembership中添加@PrePersist方法,持久化时自动将关联实体的ID同步到嵌入ID,无需手动设置:
@PrePersist public void populateId() { if (organizationMembershipId == null) { organizationMembershipId = new OrganizationMembershipId(); } organizationMembershipId.setOrganizationId(organization.getOrganizationId()); organizationMembershipId.setUserId(user.getUserId()); }
内容的提问来源于stack exchange,提问作者Clark Harris

