如何在 Spring Boot 中从双向表关系生成 DTO

Posted

技术标签:

【中文标题】如何在 Spring Boot 中从双向表关系生成 DTO【英文标题】:How to generate a DTO from a bidirectional table relation in Spring Boot 【发布时间】:2021-05-19 11:46:05 【问题描述】:

早上好,很高兴见到你。

我最近刚接触 Spring Boot,我正在使用一个 REST API,它基本上是一个带有歌曲的播放列表,基本上 REST API 应该具有以下结构。一个播放列表可以包含许多歌曲:


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

目前这是我配置实体的方式

实体歌曲

@Entity
@Table(name = "SONGS")
public class Songs

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

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

    @Column(name = "artist")
    private String artist;

    @Column(name = "album")
    private String album;

    @Column(name = "year")
    private int year;

    @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;
    
 

实体播放列表

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

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

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

    
    @Column(name="description")
    private String description;


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "playList")
    private List<Songs> songs = new ArrayList<>();


    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<Songs> getSongs() 
        return this.songs;
    

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

控制器

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

    @Autowired
    private PlayListService playListService;

    //Get playlist by id with songs belongs that playlist
    
    
    @GetMapping("/id")
    public Optional<PlayList> getPlayListByID(@PathVariable(value = "id") Long id) 

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

     
     @PostMapping("/create")
     public PlayList createPlayList(@RequestBody PlayList playList) 
         return playListService.savePlayList(playList);
     


我的班级 PlayListServiceImpl

@Service
public class PlayListServiceImpl implements PlayListService 

    @Autowired
    private PlayListRepository playListRepository;

    public Optional <PlayList> getById(Long Id) 
        Optional <PlayList> playList= playListRepository.findById(Id);

        return playList;
    

    @Override
    public PlayList savePlayList(PlayList playList) 
        return playListRepository.save(playList);
    
    

我的仓库

@Repository
public interface PlayListRepository extends JpaRepository<PlayList, Long> 

    Optional<PlayList> findById(Long Id);
    

但是,当我尝试使用 get 方法获取播放列表时,我遇到了无限递归问题:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (***Error); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (***Error) (through reference chain: com.example.api.songs.entity.PlayList[\"songs\"]->org.hibernate.collection.internal.PersistentBag[0]->com.example.api.songs.entity.Songs[\"playList\"]->com.example.api.songs.entity.PlayList[\"songs\"]->org.hibernate.

这是由于在 Songs 实体中找到 playList 字段,当尝试获取包含歌曲的播放列表时,我得到一个递归数组,而不是 playList 的 id,这正是我想要得到的,现实是我得到了这样的东西:

我可以通过在该字段中应用 JsonIgnore 来解决,但这会影响我,当我去保存播放列表时,我无法将 idPlayList 字段传递给每首歌曲以确保每首歌曲都与其各自的 idPlayList 一起保存

我认为最好的办法是在这里建立一个 DTO 来帮助我建立两个表的关系,但是,我不知道在执行 get 时我会如何以允许我的方式做到这一点从播放列表中,仅获取 idPlayList,并且在省电时通过将要与播放列表一起保存的每首歌曲中的 idPlayList,或者如果有另一种方式可以存储播放列表及其歌曲并存储它们直接,因为目前我必须由 BD 为每首歌曲分配 idPlayList

【问题讨论】:

public class Songs 必须是 Song。这是一条记录,不是所有记录:) 【参考方案1】:

    spring.jpa.open-in-view=false 添加到application.properties 以获得LazyInitializationException。这将有助于建立良好的查询。 What is this spring.jpa.open-in-view=true property in Spring Boot?

    使用带有LEFT JOIN FETCH 的JPQL 查询使用songs 加载PlayList 以获取包含列表的歌曲。所以songs 已经被加载,而歌曲中的playList 将是懒惰的。 @EntityGraph也可以使用。

    select list from PlayList list left join fetch list.songs where list.id = :listId
    

    杰克逊有Hibernate5Module。它允许省略惰性关联。所以杰克逊不会在歌曲中碰playList。 Configure Jackson to omit lazy-loading attributes in Spring Boot

    构建类似于真实系统的东西。您可以在服务级别添加PlayListResponseSongResponse 类并将实体映射到这些类。在这种情况下,您可能不需要Hibernate5Module。但最好也尝试一下这个模块。

    application.properties 中启用数据库日志记录

logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG

logging.level.org.hibernate.SQL=DEBUG
spring.jpa.properties.hibernate.use_sql_comments=true

仔细检查日志中的事务和 SQL。不要让 Hibernate 产生不必要的查询。

如何用列表保存歌曲

将此方法添加到PlayList

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

使用addSong() 方法将歌曲添加到列表中。

保存PlayList。列表中的所有歌曲都会被 Hibernate 以正确的列表 id 保存,因为 @OneToMany 中有 cascade = CascadeType.ALL

【讨论】:

感谢您的回答,您能给我举个 LEFT JOIN FETCH 的例子吗?如果你有它来映射播放列表和他们的歌曲。 我一直在阅读@EntityGraph,它非常好,因为它可以让我提高查询的性能,但是,我仍然不明白如何加载(插入)带有歌曲的播放列表,因为 playList 的 id 是自动生成和自动递增的,我不知道如何关联它,以便歌曲与其 id_playList 值一起保存,从而保持与 playList 的关联 谢谢,我要测试一下修改后告诉你 朋友怎么了?您能否帮助我解决以下与播放列表中歌曲的删除方法相关的问题***.com/questions/67527711/…

以上是关于如何在 Spring Boot 中从双向表关系生成 DTO的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Boot 应用程序中从数据库表中填充实体

如何在spring boot中从同一个应用程序调用另一个api

如何在 Spring Boot 中从资源服务器中的令牌中提取声明

如何在 Spring Boot 中从 Mongodb 读取集合数据并定期发布到 kafka 主题中

java - 如何在Java Spring中从Hibernate双向OneToMany ManyToOne中检索数据

在 Spring Boot 中从 jwt 获取附加属性