如何在 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
构建类似于真实系统的东西。您可以在服务级别添加PlayListResponse
、SongResponse
类并将实体映射到这些类。在这种情况下,您可能不需要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 主题中