Spring JPA保存User实体时出现user_id非空约束错误
解决Spring JPA/Hibernate保存User时CompanyInformation的user_id不能为空问题
问题描述
保存User实体时触发数据库约束错误,报错信息为Column 'user_id' cannot be null,该错误仅出现在CompanyInformation实体关联时,Address实体的关联保存无异常。
实体类代码
User实体类
@Entity @Table(name = "user") @Data @FieldDefaults(level = AccessLevel.PRIVATE) public class User extends Auditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id; @Column(name = "name") String name; @Column(name = "email", nullable = false) @Email String email; @Column(name = "user_type", nullable = false) @Enumerated(EnumType.STRING) UserType userType; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "address_id") @ToString.Exclude Address address; @Column(name = "active", nullable = false) Boolean active = false; @Column(name = "phone_number", nullable = false) @Pattern(regexp = "^\\\\d{10}$", message = "Phone number must be exactly 10 digits") String phoneNumber; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "company_id") @ToString.Exclude CompanyInformation companyInformation; @Column(name = "hashed_password", nullable = false) String hashedPassword; @Column(name = "kyc_done") Boolean kycDone = false; }
Address和CompanyInformation实体类
@Entity @Table(name = "address") @Data @FieldDefaults(level = AccessLevel.PRIVATE) public class Address extends Auditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @ToString.Exclude User user; @Column(name = "name", nullable = false) String addressName; @Column(name = "address1", nullable = false) String address1; @Column(name = "address2", nullable = false) String address2; @Column(name = "zipCode", nullable = false) String zipCode; @Column(name = "country", nullable = false) @Enumerated(EnumType.STRING) Country country; @Column(name = "state", nullable = false) @Enumerated(EnumType.STRING) State state; @Column(name = "email") String email; } @Entity @Table(name = "company_info") @Data @FieldDefaults(level = AccessLevel.PRIVATE) public class CompanyInformation extends Auditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @ToString.Exclude User user; @Column(name = "company_name", nullable = false) String companyName; @Column(name = "company_type", nullable = false) String companyType; @Column(name = "company_gst") String gstNumber; @Column(name = "company_pan", nullable = false) String panCardNumber; }
报错日志
2024-11-26 01:28:11.399 ERROR 97993 --- [nio-9090-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'user_id' cannot be null 2024-11-26 01:28:11.608 ERROR 97993 --- [nio-9090-exec-1] c.f.c.exception.CustomExceptionHandler : exception occurred: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
解决方法
1. 同步双向关联的引用
保存User前,必须手动设置CompanyInformation的user属性指向当前User实例,双向关联需要双方互相引用才能正确维护外键:
// 构建User和CompanyInformation实例 User user = new User(); // 设置user的基础属性... CompanyInformation companyInfo = new CompanyInformation(); // 设置companyInfo的业务属性... // 关键步骤:同步双向引用 companyInfo.setUser(user); user.setCompanyInformation(companyInfo); // 保存User(级联操作会自动保存CompanyInformation) userRepository.save(user);
Address能正常工作,大概率是因为你在业务代码中做了同样的引用同步操作,而CompanyInformation遗漏了这一步。
2. 规范一对一关联的维护方
一对一关联中,只需要在一方(User)作为拥有方维护外键,另一方(Address、CompanyInformation)通过mappedBy指定反向关联,避免双向维护外键导致的混乱:
修改CompanyInformation的关联注解:
@OneToOne(fetch = FetchType.LAZY, mappedBy = "companyInformation") @ToString.Exclude User user;
同样,Address也可以调整为:
@OneToOne(fetch = FetchType.LAZY, mappedBy = "address") @ToString.Exclude User user;
这样只有User中的@JoinColumn负责维护外键,反向关联仅用于查询,不会触发外键赋值的问题。
3. 检查数据库表结构
确认company_info表的user_id字段是否被设置为NOT NULL:
- 如果业务允许用户没有CompanyInformation,将该字段改为可空(通过手动修改表结构或设置
@Column(nullable = true)控制DDL生成); - 如果业务要求必须关联CompanyInformation,确保保存时一定会同步双向引用,保证外键字段能被正确赋值。
4. 理解级联操作的作用
cascade = CascadeType.ALL仅负责根据User的状态自动保存/更新关联的CompanyInformation,但不会自动同步双向引用的赋值,外键字段的正确填充仍然需要手动维护双方的引用关系。
内容的提问来源于stack exchange,提问作者Madhukar Choudhary
相关产品推荐
相关产品推荐

