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" }
同时有两个问题:
- 是否可以同时保存多个实体?如果可以,如何实现?
- 向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)**和双向关联的维护。结合你的代码,需要调整以下几点:
完善双向关联的维护:
你的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); }检查级联配置:
你的AlbumEntity中@OneToMany(cascade = CascadeType.ALL, mappedBy = "album")配置了CascadeType.ALL,这意味着保存Album时,会自动级联保存关联的Song实体,符合需求。但需要注意:- 如果Song对应的Singer是已存在的,
SongEntity中@ManyToOne(cascade = CascadeType.PERSIST)会尝试新建Singer,这可能不符合预期。如果Singer是已存在的,建议把CascadeType.PERSIST改为CascadeType.MERGE或者移除级联,根据业务需求调整。
- 如果Song对应的Singer是已存在的,
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数据是否需要相同逻辑?
是的,逻辑类似,但需要注意关联方向和级联配置的差异:
双向关联维护:
Song和Singer是ManyToOne和OneToMany的双向关联,如果你希望通过Song关联Singer,同样需要维护双向引用。不过通常业务中更常见的是从Singer端维护,但如果是通过Song关联已存在的Singer,只需设置Song的singerId(或singer对象)即可。级联配置调整:
你的SongEntity中@ManyToOne(cascade = CascadeType.PERSIST)意味着保存Song时会自动新建Singer,但如果你的业务是关联已存在的Singer,这个配置

