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

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略
}

原因分析

  1. 关联对象未正确赋值:SongEntity中通过AlbumEntity album维护与专辑的关联,前端传入的album_id是数值类型,默认的JSON序列化/反序列化不会自动将数值转换为对应的AlbumEntity对象,导致album属性为null,Hibernate无法填充数据库的album_id字段。
  2. 双向关联维护缺失:AlbumEntity与SongEntity是双向一对多关联,Hibernate通过mappedBy="album"指定由Song端维护关联关系,但新增时仅传入album_id未将Song对象关联到对应的Album,导致关联未生效。
  3. 实体注解与数据库约束不匹配:数据库中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:35:37