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

