Spring Data+Hibernate新增数据报错:album_id违反非空约束
新增Song时album_id字段为空的问题解决方案
问题概述
项目采用Java+Spring Data+Hibernate+PostgreSQL技术栈,查询、删除数据功能正常,但调用POST接口新增Song数据时,明明传入了"album_id":101参数,却抛出如下错误:
ERROR: null value in column "album_id" of relation "songs" violates not-null constraint
插入的记录中album_id字段实际为null。相关实体类代码如下:
AlbumEntity
@Entity @Table(name = "albums") public class AlbumEntity extends ApplicationPersistenceEntity implements Album { @Id @Column(name = "album_id") // @GeneratedValue(strategy = GenerationType.IDENTITY) private long albumId; @Column(name = "NAME") private String albumName; @Column(name = "Genre") private String genre; @ManyToOne(cascade = CascadeType.PERSIST, optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "singer_id", nullable = false) private SingerEntity singer; @OneToMany(cascade = CascadeType.ALL, mappedBy = "album") private List<SongEntity> songs; // 构造方法、getter、setter略 }
SongEntity
@Entity @Table(name = "songs") public class SongEntity extends ApplicationPersistenceEntity implements Song { @Id @Column(name = "song_id") // @GeneratedValue(strategy = GenerationType.IDENTITY) private long songId; @Column(name = "Title") private String title; @Column(name = "Content") private String content; @ManyToOne(cascade = CascadeType.PERSIST, optional = false, fetch = FetchType.LAZY) @JoinColumn(name = "singer_id", nullable = false) private SingerEntity singer; @ManyToOne(cascade = CascadeType.PERSIST, optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "album_id") private AlbumEntity album; // 构造方法、getter、setter略 }
SingerEntity
@Entity @Table(name = "singers") public class SingerEntity extends ApplicationPersistenceEntity implements Singer { @Id @Column(name = "singer_id") // @GeneratedValue(strategy = GenerationType.IDENTITY) private long singerId; @Column(name = "First_NAME") private String firstname; @Column(name = "Last_NAME") private String lastname; @Column(name = "Gender") private String gender; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "singer") private List<SongEntity> songs; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "singer") private List<AlbumEntity> albums; // 构造方法、getter、setter略 }
原因分析
- 关联对象未正确赋值:SongEntity中通过
AlbumEntity album维护与专辑的关联,前端传入的album_id是数值类型,默认的JSON序列化/反序列化不会自动将数值转换为对应的AlbumEntity对象,导致album属性为null,Hibernate无法填充数据库的album_id字段。 - 双向关联维护缺失:AlbumEntity与SongEntity是双向一对多关联,Hibernate通过
mappedBy="album"指定由Song端维护关联关系,但新增时仅传入album_id未将Song对象关联到对应的Album,导致关联未生效。 - 实体注解与数据库约束不匹配:数据库中
songs表的album_id字段设置了非空约束,但SongEntity中@JoinColumn(name = "album_id")未添加nullable=false,且@ManyToOne的optional=true,导致实体层面未做非空校验,直到数据库层面才抛出错误。
解决方案
方案一:手动查询关联实体并赋值
在新增Song的业务逻辑中,先通过传入的album_id查询对应的AlbumEntity对象,再赋值给SongEntity的album属性,最后执行保存操作:
@Service public class SongService { @Autowired private AlbumRepository albumRepository; @Autowired private SongRepository songRepository; @Autowired private SingerRepository singerRepository; public SongEntity createSong(SongDto songDto) { SongEntity song = new SongEntity(); // 基础字段赋值 song.setSongId(songDto.getSongId()); song.setTitle(songDto.getTitle()); song.setContent(songDto.getContent()); // 关联Singer实体 SingerEntity singer = singerRepository.findById(songDto.getSingerId()) .orElseThrow(() -> new IllegalArgumentException("Singer not found with id: " + songDto.getSingerId())); song.setSinger(singer); // 关联Album实体 AlbumEntity album = albumRepository.findById(songDto.getAlbumId()) .orElseThrow(() -> new IllegalArgumentException("Album not found with id: " + songDto.getAlbumId())); song.setAlbum(album); return songRepository.save(song); } }
对应的DTO类用于接收前端参数:
public class SongDto { private long songId; private String title; private String content; private long singerId; private long albumId; // getter、setter方法 }
方案二:使用DTO映射工具自动转换
可以使用MapStruct等DTO映射工具,配置映射规则,自动将DTO中的album_id转换为AlbumEntity对象:
@Mapper(componentModel = "spring") public interface SongMapper { SongMapper INSTANCE = Mappers.getMapper(SongMapper.class); @Mapping(target = "album", source = "albumId", qualifiedByName = "albumById") @Mapping(target = "singer", source = "singerId", qualifiedByName = "singerById") SongEntity toEntity(SongDto songDto); @Named("albumById") default AlbumEntity albumById(long albumId) { AlbumEntity album = new AlbumEntity(); album.setAlbumId(albumId); // 如果需要确保存在,可注入Repository查询,这里仅示例 return album; } @Named("singerById") default SingerEntity singerById(long singerId) { SingerEntity singer = new SingerEntity(); singer.setSingerId(singerId); return singer; } }
注意:如果需要验证Album是否存在,建议在映射前通过Repository查询,避免保存不存在的关联。
方案三:调整实体注解与数据库约束一致
如果数据库中songs表的album_id字段确实为非空,需要修正SongEntity的注解,确保实体层面的校验与数据库约束匹配:
@ManyToOne(cascade = CascadeType.PERSIST, optional = false, fetch = FetchType.LAZY) @JoinColumn(name = "album_id", nullable = false) private AlbumEntity album;
这样在实体保存前,Hibernate会先校验album属性是否为null,提前抛出错误,避免数据库层面的异常。
内容的提问来源于stack exchange,提问作者Khushi Yadav
相关产品推荐
相关产品推荐

