在 Spring Boot 中使用 JPA 从具有 OneToMany 关系的实体中删除对象

Posted

技术标签:

【中文标题】在 Spring Boot 中使用 JPA 从具有 OneToMany 关系的实体中删除对象【英文标题】:Delete object from an entity with a OneToMany relationship between both using JPA in Spring Boot 【发布时间】:2021-08-04 05:42:14 【问题描述】:

下午好,我正在使用 REST API,其中我有一个包含许多歌曲的播放列表,为此我使用 JPA 以及允许我在两者之间建立关系的好处。现在,如果我想删除已经添加到播放列表中的歌曲,我做不到,我在下面向您展示我的课程

类播放列表

@Entity
@Table(name = "PLAY_LIST")
public class PlayList 

    @JsonIgnore
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    //@JsonView(View.Get.class)
    @Column(name="name")
    private String name;

    //@JsonView(View.Get.class)
    @Column(name="description")
    private String description;

    //@JsonView(View.Get.class)
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "playList")
    private List<Song> songs = new ArrayList<>();

    @Transient
    public void addSong(Song song) 
        song.setPlayList(this);
        songs.add(song);
    

    @Transient
    public void removeSong(Song song) 
        songs.remove(song);
    

    public Long getId() 
        return this.id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getName() 
        return this.name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getDescription() 
        return this.description;
    

    public void setDescription(String description) 
        this.description = description;
    


    public List<Song> getSongs() 
        return this.songs;
    

    public void setSongs(List<Song> songs) 
        this.songs = songs;
    
    

这里我有添加歌曲和删除的方法,但是删除对我不起作用。

班级歌曲

Entity
@Table(name = "SONG")
public class Song

    @JsonIgnore
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    //@JsonView(View.Create.class)
    @Column(name = "title")
    private String title;

    //@JsonView(View.Create.class)
    @Column(name = "artist")
    private String artist;

    //@JsonView(View.Create.class)
    @Column(name = "album")
    private String album;

    //@JsonView(View.Create.class)
    @Column(name = "year")
    private int year;

    /* 

    Fetch: Esta propiedad se utiliza para determinar cómo debe ser cargada la entidad.
    LAZY (perezoso): Indica que la relación solo se cargará cuando la propiedad sea leída por primera vez */
    //@JsonView(View.Get.class)
    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PLAY_LIST_ID")
    private PlayList playList;


    public Long getId() 
        return this.id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getTitle() 
        return this.title;
    

    public void setTitle(String title) 
        this.title = title;
    

    public String getArtist() 
        return this.artist;
    

    public void setArtist(String artist) 
        this.artist = artist;
    

    public String getAlbum() 
        return this.album;
    

    public void setAlbum(String album) 
        this.album = album;
    

    public int getYear() 
        return this.year;
    

    public void setYear(int year) 
        this.year = year;
    

    public PlayList getPlayList() 
        return this.playList;
    

    public void setPlayList(PlayList playList) 
        this.playList = playList;
    
    

我的班级控制器

@RestController
@RequestMapping("playlist")
public class PlayListController 

   @Autowired
   private PlayListService playListService;

   @Autowired
   private SongRepository songRepository;


   // Get playlist by id with songs belongs that playlist

   @GetMapping("/get/id")
   public Optional<PlayList> getPlayListByID(@PathVariable(value = "id") Long id) 

      Optional<PlayList> playList = playListService.getById(id);
      return playList;
   

   @PostMapping("/create")
   public PlayList createPlayList(@RequestBody PlayListDTO playListDTO) 

      PlayList playList = new PlayList();

      playList.setName(playListDTO.getName());
      playList.setDescription(playListDTO.getDescription());
      playList.setSongs(new ArrayList<>());

      for (int x=0; x<playListDTO.getSongs().size(); x++) 

         Song songs=new Song();
         songs.setTitle(playListDTO.getSongs().get(x).getTitle());
         songs.setArtist(playListDTO.getSongs().get(x).getArtist());
         songs.setAlbum(playListDTO.getSongs().get(x).getAlbum());
         songs.setYear(playListDTO.getSongs().get(x).getYear());
         playList.addSong(songs);

      
        return playListService.savePlayList(playList);
     
   @PutMapping("/update/id")
   public PlayList updatePlayList(@PathVariable(value = "id") Long id,@RequestBody Song song)

      PlayList playList = playListService.getById(id).get();
      song.setPlayList(playList);
      playList.getSongs().add(song);
      playListService.savePlayList(playList);
      return playList;
   
   
   @DeleteMapping("/delete/id")
   public PlayList deletePlayList(@PathVariable(value = "id") Long id,@RequestBody Song song)
      PlayList playList =playListService.getById(id).get();
      System.out.println(playList.getSongs());
      playList.removeSong(song);
      System.out.println(playList.getSongs());
      return playList;
   


所以我存储播放列表及其歌曲,方法 POST


    "name": "Lista 1",
    "description": "Lista de reproduccion 2020 spotify",
    "songs": [
        
            "title": "Tan Enamorados",
            "artist": "CNCO",
            "album": "Tan Enamorados",
            "year": 2020
        ,
        
            "title": "Hawai",
            "artist": "Maluma",
            "album": "PAPI JUANCHO",
            "year": 2020
        
    ]

现在,为了消除我正在传递播放列表的 ID 和歌曲的请求(没有在 BD 中自动创建的歌曲 ID 的对象),但是,我无法从播放列表中消除歌曲,并且在它返回的日志级别在控制台中打印

例如,我想删除以下歌曲:

但是它没有被删除,它返回相同的列表给我

我是否遗漏了一些东西才能删除歌曲而无需删除播放列表?

【问题讨论】:

【参考方案1】:

使用PlayList 中的所有歌曲列表删除一首歌曲并不是一个好主意。 @OneToMany 关联没有连接表。所以我们可以更简单地删除一首歌曲,使用SONG 表(这是@OneToMany 的连接表不方便的主要原因)。

为此,您需要一个歌曲 ID,并且您需要使用 CrudRepository.deleteById() 方法。 您可以为此使用完整的组合(标题、艺术家、专辑、年份),但将歌曲 ID 添加到 JSON 要简单得多。

最好使用此端点 URL 删除歌曲

/playListId/songs/songId

URL 中不需要delete 部分,您已经使用了 DELETE HTTP 方法。

为什么您的代码不起作用

    使用列表中的删除方法不正确
@Transient
public void removeSong(Song song) 
    songs.remove(song);

songs.remove() 在列表中找不到songList.remove() 通过引用找到一首歌曲。它需要有一个开放的持久上下文并从中获取一首歌曲才能在列表中找到它。

    不使用事务(打开的持久性上下文)让 Hibernate 知道歌曲已被删除并且 Hibernate 必须更新数据库。

所以一个有效的场景

start @Transactional
  Spring starts a database transaction
  Spring opens a persistent context
  load PlayList
  load a song from the database (using id or other song attributes)
  delete a song from PlayList songs (or delete a song from PlayList songs using id)
end @Transactional
    
Hibernate flushes changes and delete a song in the database
persistent context is closed
database transaction is committed

【讨论】:

非常感谢,我发现我删除歌曲的好方法是通过对象 Song 和方法 http delete 删除,发送歌曲 ID。使用 OneToMany 有什么缺点? 我很好奇 Spotify 播放列表如何通过关系在现实生活中发挥作用,这就是我试图做的事情 @CesarJusto 如果您想使用@OneToMany 收藏删除一首歌曲,您必须从数据库中加载所有歌曲,从收藏中删除一首歌曲并保存包含歌曲的播放列表。 Hibernate 会删除一条记录,当然不会更新所有内容。 @CesarJusto 使用歌曲收藏很有用,如果用户更改了收藏中的一些元素,那么您可以使用merge() 方法添加或删除多个元素。 用@OneToMay删除一首歌曲,我用实体歌曲和remove方法删除一首歌曲,然后用相同的id再次保存列表?或者在这种情况下的程序,因为我尝试使用歌曲实体中的删除功能并通过保存功能再次保存但它仍然没有返回更新的列表,或者我不知道它是否创建了一个新列表

以上是关于在 Spring Boot 中使用 JPA 从具有 OneToMany 关系的实体中删除对象的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Boot 自动装配具有 jpa 和非 jpa 特征的多个数据源

具有多个数据源和外部配置的 Spring Boot,Spring JPA

具有 JPA 依赖关系的 Flyway Spring Boot Autowired Bean

JPA 左连接查询不会在 Spring Boot 中实例化子集合?

使用spring-boot在Jpa查询中出错

Spring Boot:整合Spring Data JPA