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

Spring Boot多对一关联实体无法保存至PostgreSQL问题排查

问题:多对一关联保存时关联字段为空,触发非空约束异常

我定义了AppUser和Teacher两个实体类,Teacher与AppUser为多对一关联关系。执行保存逻辑时,先保存AppUser,再将关联的AppUser实例设置到Teacher后保存,却持续抛出异常,提示teacher表的app_user_id字段为null,违反非空约束。


实体类代码

AppUser 实体

@Entity
@Table(name = "app_user", schema = "edu", catalog = "ed_db")
@Getter
@Setter
public class AppUser {
   @Basic
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id", insertable = false, updatable = false)
    private Integer id;
    @Id
    @Column(name = "username")
    private String username;
    @Basic
    @Column(name = "password")
    @JsonIgnore
    private String password;
    @Basic
    @Column(name = "first_name")
    private String firstName;
    @Basic
    @Column(name = "last_name")
    private String lastName;
    @Basic
    @Column(name = "email")
    private String email = "";

}

Teacher 实体

@Entity
@Getter
@Setter
public class Teacher {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @Column(name = "id")
    private Integer id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "app_user_id", nullable = false, referencedColumnName = "id")
    private AppUser appUser;

    @Basic
    @Column(name = "client_id")
    @NotNull(message = "client.is_required")
    private Integer clientId;

}

业务逻辑代码

public Teacher save(TeacherDto teacherDto) {
        AppUser appUser = teacherDto.getAppUserEntity();
        AppUser dbAppUser = appUserService.save(appUser);
        Teacher teacher = teacherDto.getTeacherEntity();
        teacher.setAppUser(dbAppUser);
        teacher.setClientId(appUser.getClientId());

        return teacherRepository.save(teacher);
    }

异常信息

2022-08-19T01:20:56.052-04:00  WARN 69525 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23502
2022-08-19T01:20:56.053-04:00 ERROR 69525 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: null value in column "app_user_id" of relation "teacher" violates not-null constraint
  Detail: Failing row contains (1, null, 2, 2022-08-19 01:20:56.028, 2022-08-19 01:20:56.028).
2022-08-19T01:20:56.064-04:00 ERROR 69525 --- [nio-8080-exec-2] c.c.p.exceptions.GlobalExceptionHandler  : org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [app_user_id" of relation "teacher]

控制台输出SQL

insert 
    into
        edu.app_user
        (address, client_id, created_at, email, enabled, first_name, last_name, password, phone, username) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
insert 
    into
        edu.teacher
        (app_user_id, client_id, created_at, updated_at) 
    values
        (?, ?, ?, ?)

问题原因与解决方案

核心原因

AppUser实体中,id字段被标记为@Basic且设置了insertable = false, updatable = false,同时username被设为@Id(主键)。而Teacher的@JoinColumn关联的是AppUser的id字段,但该字段由数据库序列生成,且insertable = false会让Hibernate忽略对这个字段的处理,导致保存后的dbAppUser实例中id仍然为null。

当保存Teacher时,Hibernate尝试获取dbAppUser.id作为app_user_id的值,自然得到null,触发非空约束异常。

解决方案

方案一:修正AppUser的主键配置(推荐)

将id设为真正的主键,username改为唯一约束的普通字段:

@Entity
@Table(name = "app_user", schema = "edu", catalog = "ed_db")
@Getter
@Setter
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id")
    private Integer id;
    
    @Column(name = "username", unique = true, nullable = false)
    private String username;
    
    @Basic
    @Column(name = "password")
    @JsonIgnore
    private String password;
    
    @Basic
    @Column(name = "first_name")
    private String firstName;
    
    @Basic
    @Column(name = "last_name")
    private String lastName;
    
    @Basic
    @Column(name = "email")
    private String email = "";
}

修改后,保存AppUser时Hibernate会自动将数据库生成的id回填到dbAppUser实例中,设置到Teacher后就能正确传递app_user_id值。

方案二:保留username为主键的适配

若必须以username作为AppUser的主键,需修改Teacher的关联配置,让@JoinColumn关联到username字段:

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "app_user_id", nullable = false, referencedColumnName = "username")
private AppUser appUser;

同时要确保teacher表的app_user_id字段类型与username一致(比如varchar类型),这样保存时Hibernate会使用AppUser.username作为关联值,避免null问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:09:35