添加歌曲后Spring Boot获取歌曲接口报NoSuchElementException及500错误
问题分析与解决:Spring Boot歌曲专辑一对多关系NoSuchElementException错误
我们开发Spring Boot应用时,要求手动在Controller中映射DTO(禁止使用外部库)来管理歌曲与专辑的一对多关系,使用PostgreSQL数据库,pgAdmin中能正常查看数据,但添加新歌曲后调用GET /song/all接口会触发NoSuchElementException,异常指向SongController.java第55行代码songs.add(new SongDTO((Song) iterator.next(), iterator.next().getAlbum()));,Postman返回500内部服务器错误。初始状态下GET接口可正常调用,添加歌曲后出现错误。
相关代码展示
SongDTO
import com.newplaylist.entity.Album; import com.newplaylist.entity.Song; public class SongDTO { private Integer id; private String author, title; private AlbumDTO album; public SongDTO() { } public SongDTO(Song song) { this.id = song.getId(); this.author = song.getAuthor(); this.title = song.getTitle(); } public SongDTO(Song song, Album album) { this.id = song.getId(); this.author = song.getAuthor(); this.title = song.getTitle(); this.album = new AlbumDTO(album, true); } // getters and setters... }
AlbumDTO
import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import com.newplaylist.entity.Album; import com.newplaylist.entity.Song; public class AlbumDTO { private Integer id; private String name; private String production; private Set<SongDTO> songs; public AlbumDTO() { } public AlbumDTO(Album album) { this.id = album.getId(); this.name = album.getName(); this.production = album.getProduction(); } public AlbumDTO(Album album, boolean lazy) { this.id = album.getId(); this.name = album.getName(); this.production = album.getProduction(); songs = new HashSet<SongDTO>(); album.getSongs(); if (!lazy) { for (Iterator<Song> iterator = album.getSongs().iterator(); iterator.hasNext();) { songs.add(new SongDTO((Song) iterator.next())); } } } // getters and setters... }
SongController
package com.newplaylist.controller; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.newplaylist.dto.AlbumDTO; import com.newplaylist.dto.SongDTO; import com.newplaylist.entity.Album; import com.newplaylist.entity.Song; import com.newplaylist.service.SongService; @RestController @RequestMapping("/song") public class SongController { @Autowired SongService songService; public void init() { } @PostMapping("/add") public ResponseEntity<?> addSong(@RequestBody Song song) { try { return new ResponseEntity<SongDTO>(new SongDTO(songService.addSong(song), song.getAlbum()), HttpStatus.CREATED); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<Object>(e.getStackTrace(), HttpStatus.INTERNAL_SERVER_ERROR); } } @GetMapping("/all") public ResponseEntity<Collection<SongDTO>> getSongs() { Collection<SongDTO> songs = new ArrayList<>(); try { for (Iterator<Song> iterator = songService.getSongs().iterator(); iterator.hasNext();) { songs.add(new SongDTO((Song) iterator.next(), iterator.next().getAlbum())); } return new ResponseEntity<Collection<SongDTO>>(songs, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); } } }
Album(实体类)
package com.newplaylist.entity; import java.util.Set; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; @Entity public class Album { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private String production; //@JsonBackReference // per scegliere quale reference stampare, altrimenti andrebbe in loop infinito @OneToMany(mappedBy = "album", cascade = CascadeType.ALL) private Set<Song> songs; public Album() { super(); } public Album(Integer id, String name, String production, Set<Song> songs) { super(); this.id = id; this.name = name; this.production = production; this.songs = songs; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getProduction() { return production; } public void setProduction(String production) { this.production = production; } public Set<Song> getSongs() { return songs; } public void setSongs(Set<Song> songs) { this.songs = songs; } }
Song(实体类)
import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @Entity public class Song { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String author, title; @ManyToOne @JoinColumn(name = "album", nullable = false) //@JsonManagedReference private Album album; public Song() {} public Song(Integer id, String author, String title) { super(); this.id = id; this.author = author; this.title = title; } public Song(Integer id, String author, String title, Album album) { super(); this.id = id; this.author = author; this.title = title; this.album = album; } // getters and setters }
核心问题原因
SongController的getSongs()方法中,循环迭代歌曲集合时每次循环调用了两次iterator.next():
- 第一次
iterator.next()获取当前Song对象 - 第二次
iterator.next()尝试获取下一个元素的Album,但当迭代到最后一个元素时,没有下一个元素,直接抛出NoSuchElementException - 同时,这种写法会跳过一半的歌曲元素,因为每次循环消耗两个元素
修复方案
修改SongController的getSongs()方法,每次循环只调用一次iterator.next(),获取当前Song对象后,直接从该对象中获取关联的Album:
@GetMapping("/all") public ResponseEntity<Collection<SongDTO>> getSongs() { Collection<SongDTO> songs = new ArrayList<>(); try { for (Iterator<Song> iterator = songService.getSongs().iterator(); iterator.hasNext();) { Song song = iterator.next(); // 仅调用一次next()获取当前歌曲 songs.add(new SongDTO(song, song.getAlbum())); // 从当前歌曲直接获取关联专辑 } return new ResponseEntity<Collection<SongDTO>>(songs, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); } }
额外优化建议
- 简化迭代写法:用增强for循环替代Iterator,代码更简洁:
@GetMapping("/all") public ResponseEntity<Collection<SongDTO>> getSongs() { Collection<SongDTO> songs = new ArrayList<>(); try { for (Song song : songService.getSongs()) { songs.add(new SongDTO(song, song.getAlbum())); } return new ResponseEntity<>(songs, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); } }
- DTO构造优化:在
SongDTO的SongDTO(Song song)构造方法中直接初始化AlbumDTO,减少参数传递冗余:
public SongDTO(Song song) { this.id = song.getId(); this.author = song.getAuthor(); this.title = song.getTitle(); this.album = new AlbumDTO(song.getAlbum(), true); // 直接从当前song获取关联专辑 }
修改后Controller中可简化为:songs.add(new SongDTO(song));
- JSON循环引用处理:给实体类的关联关系添加
@JsonBackReference和@JsonManagedReference,避免序列化时出现循环引用问题:
- 在
Album类的@OneToMany注解上添加@JsonBackReference - 在
Song类的@ManyToOne注解上添加@JsonManagedReference
内容的提问来源于stack exchange,提问作者materialAres
相关产品推荐
相关产品推荐

