Spring JPA(Hibernate)中的双向多对多关系

mi7gmzs6  于 2023-10-23  发布在  Spring
关注(0)|答案(1)|浏览(154)

我在使用Spring Data JPA和Hibernate作为JPA提供程序的多对多双向Map方面遇到了麻烦。双向关系Map允许您在两个方向上导航关联,这正是我所需要的。我有两个实体:ArtistSong。当我得到一个艺术家,我想得到他所有的歌曲,反之亦然,当我得到一首歌,我想得到这首歌的艺术家。这会导致一个递归错误,正如我所研究的那样,可以使用@JsonIdentityInfo或@JsonIgnore解决,但如果它们没有给予我真正想要的JSON,就不会解决。
艺术家实体:

@Entity
@Table(name = "artist")
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")
public class Artist {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

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

    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(name = "artist_songs", joinColumns = { @JoinColumn(name = "artist_id") }, inverseJoinColumns = {
            @JoinColumn(name = "song_id") })
    private Set<Song> songs = new HashSet<>();
}

歌曲实体:

@Entity
@Table(name = "song")
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")
public class Song {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

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

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

    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.PERSIST,
                    CascadeType.MERGE
            },
            mappedBy = "songs")
    private Set<Artist> artists = new HashSet<>();
}

我的问题是,当我调用这些实体时,我得到了这些JSON对象:

// THE JSON I GET WHEN I CALL http://localhost:8080/api/artists
[
    {
        "id": 1,
        "name": "artist1",
        "image": "artist1.png",
        "songs": [
            {
                "id": 1,
                "title": "song 1",
                "src": "spng1.mp3",
                "image": "song1.png",
                "artists": [
                    1
                ]
            },
            {
                "id": 2,
                "title": "song 2",
                "src": "song2.mp3",
                "image": "song2.png",
                "artists": [
                    1
                ]
            }
        ]
    },
    {
        "id": 2,
        "name": "artist2",
        "image": "artist2.png",
        "songs": [
            {
                "id": 3,
                "title": "song 3",
                "src": "song3.mp3",
                "image": "song3.png",
                "artists": [
                    2
                ]
            }
        ]
    }
]

// THE JSON I GET WHEN I CALL http://localhost:8080/api/songs

[
    {
        "id": 1,
        "title": "song 1",
        "src": "spng1.mp3",
        "image": "song1.png",
        "artists": [
            {
                "id": 1,
                "name": "artist1",
                "image": "artist1.png",
                "songs": [
                    {
                        "id": 2,
                        "title": "song 2",
                        "src": "song2.mp3",
                        "image": "song2.png",
                        "artists": [
                            1
                        ]
                    },
                    1
                ]
            }
        ]
    },
    2,
    {
        "id": 3,
        "title": "song 3",
        "src": "song3.mp3",
        "image": "song3.png",
        "artists": [
            {
                "id": 2,
                "name": "artist2",
                "image": "artist2.png",
                "songs": [
                    3
                ]
            }
        ]
    }
]

我想要的JSON对象是这样的:

// THE ARTIST ENTITY
{
        "id": 2,
        "name": "artist2",
        "image": "artist2.png",
        "songs": [
            {
              "id": 1,
              "title": "song 1",
              "src": "song1.mp3",
              "image": "song1.png",
//REMOVES THE FIELD artists INSIDE SONG. KEEPS THAT FIELD ONLY WHEN I MAKE THE CALL TO GET ALL SONGS
            },
            {
              "id": 2,
              "title": "song 2",
              "src": "song2.mp3",
              "image": "song2.png",
//REMOVES THE FIELD artists INSIDE SONG. KEEPS THAT FIELD ONLY WHEN I MAKE THE CALL TO GET ALL SONGS
            }
        ]
    }


// THE SONG ENTITY
{
        "id": 1,
        "title": "song 1",
        "src": "song1.mp3",
        "image": "song1.png",
        "artists": [
            {
                "id": 2,
                "name": "artist2",
                "image": "artist2.png",
//REMOVES THE FIELD songs INSIDE ARTIST. KEEPS THAT FIELD ONLY WHEN I MAKE THE CALL TO GET ALL ARTISTS
            }
        ]
    }
zkure5ic

zkure5ic1#

您正在查找的Jackson注解是@JsonManagedReference@JsonBackReference的组合。你可以把它放在关系的两端。但是,它只能用于 * 一对一 一对多 * 和 * 多对一 * 关系。不是“多对多”。
这就是为什么你应该在结构上将你的双向 * 多对多 * 关系分解为两个 * 多对一 * 关系(正如@Rogue的评论中所建议的那样)。

这是完整的解决方案

首先我们定义一个复合键类:

@Embeddable
class ArtistSongKey implements Serializable {

    Long artistId;

    Long songId;

    // standard constructors, getters, and setters
    // hashcode and equals implementation
}

使用这个复合键类,我们将创建实体类,它对连接表进行建模:

@Entity
class ArtistSong {

    @EmbeddedId
    ArtistSongKey id;

    @ManyToOne
    @JsonBackReference
    @MapsId("artistId")
    Artist artist;

    @ManyToOne
    @JsonBackReference
    @MapsId("songId")
    Song song;

    // standard constructors, getters, and setters
}

在此之后,我们可以在Artist和Song实体中配置反向引用:

class Artist {

    @OneToMany(mappedBy = "song")
    @JsonManagedReference
    Set<ArtistSong> songsOfArtist;

    // ...
}

class Song {

    @OneToMany(mappedBy = "artist")
    @JsonManagedReference
    Set<ArtistSong> artistsOfSong;

    // ...
}

请注意添加的Jackson注解,它应该修复递归错误,并提供JSON对象的所需布局。
基于:https://www.baeldung.com/jpa-many-to-many

相关问题