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

添加歌曲后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);
    }
}

额外优化建议

  1. 简化迭代写法:用增强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);
    }
}
  1. 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));

  1. JSON循环引用处理:给实体类的关联关系添加@JsonBackReference和@JsonManagedReference,避免序列化时出现循环引用问题:
  • 在Album类的@OneToMany注解上添加@JsonBackReference
  • 在Song类的@ManyToOne注解上添加@JsonManagedReference

内容的提问来源于stack exchange,提问作者materialAres

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 06:10:34