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

SpringBoot中如何向Album实体添加歌曲?多实体保存逻辑咨询

需求与问题

我需要实现向Album实体添加歌曲的功能,希望通过Postman发送如下JSON数据时,数据能分别保存到Album和Song表中:

{
    "id": 102,
    "albumId": 102,
    "albumName": "HS1",
    "genre": "Pop",
    "singerId":202,

    "songs":[
        {
            "id": 302,
            "songId": 302,
            "title": "Bad blood",
            "singerId": 201,
            "duration": 3.76
        },
        {
            "id": 303,
            "songId": 303,
            "title": "Happy",
            "singerId": 202,
            "duration": 3.54
        }
    ],
    "albumreleaseDate":"2019-05-16"
}

同时有两个问题:

  1. 是否可以同时保存多个实体?如果可以,如何实现?
  2. 向Song实体关联Singer数据是否需要相同逻辑?

以下是我的实体类代码:

Album 实体类

@Entity
@Table(name = "albums")
public class AlbumEntity extends ApplicationPersistenceEntity implements Album {

  @Id
  @Column(name = "album_id")
  @NotNull
  private long albumId;

  @NotEmpty
  @Column(name = "NAME")
  private String albumName;

  @NotEmpty
  @Column(name = "Genre")
  private String genre;

  @Column(name = "album_release_date", columnDefinition = "DATE", nullable = false)
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date albumreleaseDate;

  @ManyToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
  @JoinColumn(name = "singer_id", nullable = false)
  private SingerEntity singer;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "album")
  @Fetch(FetchMode.JOIN)
  private List<SongEntity> songs;

  private static final long serialVersionUID = 1L;

  /**
   * The constructor.
   */
  public AlbumEntity() {

  }

  /**
   * The constructor.
   *
   * @param albumId
   * @param albumName
   * @param genre
   * @param albumreleaseDate
   * @param singer
   * @param songs
   */
  public AlbumEntity(long albumId, String albumName, String genre, Date albumreleaseDate, SingerEntity singer,
      List<SongEntity> songs) {

    super();
    this.albumId = albumId;
    this.albumName = albumName;
    this.genre = genre;
    this.albumreleaseDate = albumreleaseDate;
    this.singer = singer;
    this.songs = songs;
  }

  /**
   * @return albumId
   */
  @Override
  public long getAlbumId() {

    return this.albumId;
  }

  /**
   * @param albumId new value of {@link #getalbumId}.
   */
  @Override
  public void setAlbumId(long albumId) {

    this.albumId = albumId;
  }

  /**
   * @return albumName
   */
  @Override
  public String getAlbumName() {

    return this.albumName;
  }

  /**
   * @param albumName new value of {@link #getalbumName}.
   */
  @Override
  public void setAlbumName(String albumName) {

    this.albumName = albumName;
  }

  /**
   * @return genre
   */
  @Override
  public String getGenre() {

    return this.genre;
  }

  /**
   * @param genre new value of {@link #getgenre}.
   */
  @Override
  public void setGenre(String genre) {

    this.genre = genre;
  }

  /**
   * @return albumreleaseDate
   */
  @Override
  public Date getAlbumreleaseDate() {

    return this.albumreleaseDate;
  }

  /**
   * @param albumreleaseDate new value of {@link #getalbumreleaseDate}.
   */
  @Override
  public void setAlbumreleaseDate(Date albumreleaseDate) {

    this.albumreleaseDate = albumreleaseDate;
  }

  /**
   * @return singer
   */
  public SingerEntity getSinger() {

    return this.singer;
  }

  /**
   * @param singer new value of {@link #getsinger}.
   */
  public void setSinger(SingerEntity singer) {

    this.singer = singer;
  }

  /**
   * @return songs
   */
  public List<SongEntity> getSongs() {

    return this.songs;
  }

  /**
   * @param songs new value of {@link #getsongs}.
   */
  public void setSongs(List<SongEntity> songs) {

    this.songs = songs;
  }

  @Override
  @Transient
  public Long getSingerId() {

    if (this.singer == null) {
      return null;
    }
    return this.singer.getId();
  }

  @Override
  public void setSingerId(Long singerId) {

    if (singerId == null) {
      this.singer = null;
    } else {
      SingerEntity singerEntity = new SingerEntity();
      singerEntity.setId(singerId);
      this.singer = singerEntity;
    }
  }

  @Override
  public String toString() {

    return "AlbumEntity [albumId=" + this.albumId + ", albumName=" + this.albumName + ", genre=" + this.genre
        + ", albumreleaseDate=" + this.albumreleaseDate + ", singer=" + this.singer + ", songs=" + this.songs + "]";
  }

}

Song 实体类

@Entity
@Table(name = "songs")
public class SongEntity extends ApplicationPersistenceEntity implements Song {

  @Id
  @Column(name = "song_id")
  // @GeneratedValue(strategy = GenerationType.TABLE)
  private long songId;

  @Column(name = "Title")
  private String title;

  @Column(name = "duration")
  private float duration;

  /**
   * @return duration
   */
  @Override
  public float getDuration() {

    return this.duration;
  }

  /**
   * @param duration new value of {@link #getduration}.
   */
  @Override
  public void setDuration(float duration) {

    this.duration = duration;
  }

  @ManyToOne(cascade = CascadeType.PERSIST, optional = false, fetch = FetchType.LAZY)
  @JoinColumn(name = "singer_id", nullable = false)
  private SingerEntity singer;

  @ManyToOne(cascade = CascadeType.ALL, optional = true, fetch = FetchType.LAZY)
  @JoinColumn(name = "album_id")
  private AlbumEntity album;

  private static final long serialVersionUID = 1L;

  /**
   * The constructor.
   */
  public SongEntity() {

  }

  /**
   * The constructor.
   *
   * @param songId
   * @param title
   * @param content
   * @param singer
   * @param album
   */
  public SongEntity(long songId, String title, SingerEntity singer, AlbumEntity album) {

    super();
    this.songId = songId;
    this.title = title;
    this.singer = singer;
    this.album = album;
  }

  /**
   * @return songId
   */
  @Override
  public long getSongId() {

    return this.songId;
  }

  /**
   * @param songId new value of {@link #getsongId}.
   */
  @Override
  public void setSongId(long songId) {

    this.songId = songId;
  }

  /**
   * @return title
   */
  @Override
  public String getTitle() {

    return this.title;
  }

  /**
   * @param title new value of {@link #gettitle}.
   */
  @Override
  public void setTitle(String title) {

    this.title = title;
  }

  /**
   * @return singer
   */
  public SingerEntity getSinger() {

    return this.singer;
  }

  /**
   * @param singer new value of {@link #getsinger}.
   */
  public void setSinger(SingerEntity singer) {

    this.singer = singer;
  }

  /**
   * @return album
   */
  public AlbumEntity getAlbum() {

    return this.album;
  }

  /**
   * @param album new value of {@link #getalbum}.
   */
  public void setAlbum(AlbumEntity album) {

    this.album = album;
  }

  @Override
  @Transient
  public Long getSingerId() {

    if (this.singer == null) {
      return null;
    }
    return this.singer.getId();
  }

  @Override
  public void setSingerId(Long singerId) {

    if (singerId == null) {
      this.singer = null;
    } else {
      SingerEntity singerEntity = new SingerEntity();
      singerEntity.setId(singerId);
      this.singer = singerEntity;
    }
  }

  @Override
  @Transient
  public Long getAlbumId() {

    if (this.album == null) {
      return null;
    }
    return this.album.getId();
  }

  @Override
  public void setAlbumId(Long albumId) {

    if (albumId == null) {
      this.album = null;
    } else {
      AlbumEntity albumEntity = new AlbumEntity();
      albumEntity.setId(albumId);
      this.album = albumEntity;
    }
  }

  @Override
  public String toString() {

    return "SongEntity [songId=" + this.songId + ", title=" + this.title + ", duration=" + this.duration + ", singer="
        + this.singer + ", album=" + this.album + "]";
  }

}

Singer 实体类

@Entity
@Table(name = "singers")
public class SingerEntity extends ApplicationPersistenceEntity implements Singer {

  @Id
  @Column(name = "singer_id")
  private long singerId;

  @NotEmpty
  @Column(name = "singer_name")
  private String singerName;

  @NotEmpty
  @Column(name = "Gender")
  private String gender;

  @Column(name = "birth_date", columnDefinition = "DATE", nullable = false)
  @JsonFormat(pattern = "yyyy-MM-dd")
  private Date birthDate;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "singer")
  private List<SongEntity> songs;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "singer")
  private List<AlbumEntity> albums;

  private static final long serialVersionUID = 1L;

  /**
   * The constructor.
   */
  public SingerEntity() {

  }

  /**
   * The constructor.
   *
   * @param singerId
   * @param firstname
   * @param lastname
   * @param gender
   * @param songs
   * @param albums
   */
  public SingerEntity(long singerId, String singerName, String gender, Date birthDate, List<SongEntity> songs,
      List<AlbumEntity> albums) {

    super();
    this.singerId = singerId;
    this.singerName = singerName;
    this.gender = gender;
    this.birthDate = birthDate;
    this.songs = songs;
    this.albums = albums;
  }

  /**
   * @return singerId
   */
  @Override
  public long getSingerId() {

    return this.singerId;
  }

  /**
   * @param singerId new value of {@link #getsingerId}.
   */
  @Override
  public void setSingerId(long singerId) {

    this.singerId = singerId;
  }

  /**
   * @return firstname
   */
  @Override
  public String getSingerName() {

    return this.singerName;
  }

  /**
   * @param singername new value of {@link #getsingername}.
   */
  @Override
  public void setSingerName(String singerName) {

    this.singerName = singerName;
  }

  /**
   * @return gender
   */
  @Override
  public String getGender() {

    return this.gender;
  }

  /**
   * @param gender new value of {@link #getgender}.
   */
  @Override
  public void setGender(String gender) {

    this.gender = gender;
  }

  /**
   * @return birthDate
   */
  @Override
  public Date getBirthDate() {

    return this.birthDate;
  }

  /**
   * @param birthDate new value of {@link #getbirthDate}.
   */
  @Override
  public void setBirthDate(Date birthDate) {

    this.birthDate = birthDate;
  }

  /**
   * @return songs
   */
  public List<SongEntity> getSongs() {

    return this.songs;
  }

  /**
   * @param songs new value of {@link #getsongs}.
   */
  public void setSongs(List<SongEntity> songs) {

    this.songs = songs;
  }

  /**
   * @return albums
   */
  public List<AlbumEntity> getAlbums() {

    return this.albums;
  }

  /**
   * @param albums new value of {@link #getalbums}.
   */
  public void setAlbums(List<AlbumEntity> albums) {

    this.albums = albums;
  }

  @Override
  public String toString() {

    return "SingerEntity [singerId=" + this.singerId + ", singerName=" + this.singerName + ", gender=" + this.gender
        + ", birthDate=" + this.birthDate + ", songs=" + this.songs + ", albums=" + this.albums + "]";
  }

}

解答

问题1:是否可以同时保存多个实体?如果可以,如何实现?

可以同时保存多个关联实体,核心是利用JPA的**级联操作(Cascade)**和双向关联的维护。结合你的代码,需要调整以下几点:

  1. 完善双向关联的维护:
    你的AlbumEntity和SongEntity已经配置了@OneToMany和@ManyToOne关联,但在保存时需要确保子实体(Song)关联到父实体(Album)。在设置Album的songs列表时,要给每个Song设置对应的Album引用:

    // Service层处理请求示例
    public Album saveAlbumWithSongs(Album album) {
        for (Song song : album.getSongs()) {
            song.setAlbum(album); // 维护双向关联
        }
        return albumRepository.save(album);
    }
    
  2. 检查级联配置:
    你的AlbumEntity中@OneToMany(cascade = CascadeType.ALL, mappedBy = "album")配置了CascadeType.ALL,这意味着保存Album时,会自动级联保存关联的Song实体,符合需求。但需要注意:

    • 如果Song对应的Singer是已存在的,SongEntity中@ManyToOne(cascade = CascadeType.PERSIST)会尝试新建Singer,这可能不符合预期。如果Singer是已存在的,建议把CascadeType.PERSIST改为CascadeType.MERGE或者移除级联,根据业务需求调整。
  3. Controller层接收数据:
    确保Controller能正确接收包含songs列表的JSON,使用@RequestBody注解绑定到Album对象:

    @PostMapping("/albums")
    public ResponseEntity<Album> createAlbum(@RequestBody Album album) {
        Album savedAlbum = albumService.saveAlbumWithSongs(album);
        return ResponseEntity.ok(savedAlbum);
    }
    

问题2:向Song实体关联Singer数据是否需要相同逻辑?

是的,逻辑类似,但需要注意关联方向和级联配置的差异:

  1. 双向关联维护:
    Song和Singer是ManyToOne和OneToMany的双向关联,如果你希望通过Song关联Singer,同样需要维护双向引用。不过通常业务中更常见的是从Singer端维护,但如果是通过Song关联已存在的Singer,只需设置Song的singerId(或singer对象)即可。

  2. 级联配置调整:
    你的SongEntity中@ManyToOne(cascade = CascadeType.PERSIST)意味着保存Song时会自动新建Singer,但如果你的业务是关联已存在的Singer,这个配置

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:15:32