Search code examples
javamysqlpostmanmany-to-many

Entity of @ManyToMany doesn't saved with Spring-Boot & Spring-JPA


I'm using Spring Boot and MySQL. I tried to add new entity(songs) in playlist table. They have many to many relationship. But as you can see in answer after mysql query it doesn't saved. Other relationships work correctly

PlaylistEntity

@Data
@Entity
@Component
@Getter
@EqualsAndHashCode(exclude = "songs")
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "playlists")
public class PlaylistEntity {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String playlistTitle;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
    @JoinColumn(name = "user_id", nullable = false)
    private UserEntity user;

    private LocalDateTime createdAt;

    public PlaylistEntity(String playlistTitle, UserEntity user, LocalDateTime createdAt) {
        this.playlistTitle = playlistTitle;
        this.user = user;
        this.createdAt = createdAt;
    }

    @Transient
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "playlist_song",
            joinColumns = @JoinColumn(name = "playlist_id", nullable=false),
            inverseJoinColumns = @JoinColumn(name = "song_id", nullable=false))
    private Set<SongEntity> songs = new HashSet<>();

}

PlaylistRepository

@Repository
public interface PlaylistRepository extends PagingAndSortingRepository<PlaylistEntity, String> {
    @Query(value = "select * from playlists where user_id = :id", nativeQuery = true)
    List<PlaylistEntity> showAllUserPlaylists(@Param("id") String id);

    @Query(value = "select * from playlists where playlist_title = :playlist_title", nativeQuery = true)
    PlaylistEntity findByName(@Param("playlist_title") String playlist_title);
}

SongEntity

@Data
@Entity
@Builder
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "songs")
public class SongEntity {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String title;

    private String artist;

    private String album;

    private String genre;

    @CreationTimestamp
    private LocalDateTime releaseDate;

    private int likes;

    @Transient
    @EqualsAndHashCode.Exclude
    @ManyToMany(mappedBy = "songs")
    private Set<PlaylistEntity> playlistEntities = new HashSet<>();

    @Transient
    @ManyToMany(mappedBy = "songs")
    @EqualsAndHashCode.Exclude
    private Set<SubscriptionEntity> subscriptionEntities = new HashSet<>();

    public SongEntity(String name) {
        this.title = name;
    }
}

SongRepository

@Repository
public interface SongRepository extends JpaRepository<SongEntity, String> {

    @Query(value="SELECT * FROM songs WHERE (:genre is null or genre = :genre) " +
            "AND (:artist IS NULL or artist = :artist)", nativeQuery=true)
    List<SongEntity> findByParams(@Param("genre") String genre, @Param("artist") String artist);

    @Query(value="SELECT * FROM songs WHERE artist = :artist", nativeQuery=true)
    List<SongEntity> findByArtist(@Param("artist") String artist);

    @Query(value="SELECT * FROM songs WHERE genre = :genre", nativeQuery=true)
    List<SongEntity> findByGenre(@Param("genre") String genre);

    @Query(value = "SELECT s.title, s.likes FROM SongEntity s WHERE s.artist = :artist")
    List<SongEntity> showSongsStatistics(@Param("artist") String artist);
}

Method where I saved song in playlist table

@Transactional
public Playlist addSongToPlaylist(String playlistId, String songId) throws Exception {

    SongEntity addedSong = findSongById(songId)
    PlaylistEntity requiredPlaylist = findPlaylistById(playlistId);

    requiredPlaylist.getSongs().add(addedSong);

    PlaylistEntity updatedPlaylist = playlistRepository.save(requiredPlaylist);

    return playlistConverter.fromEntity(updatedPlaylist);
}

And controller

@Slf4j
@Configuration
@RestController
@AllArgsConstructor
@RequestMapping("/user/playlists")
public class PlaylistController {

    private final PlaylistService playlistService;

    @PostMapping(value = ADD_SONG_TO_PLAYLIST_URL)
    Playlist addSongToThePlaylist(@RequestParam String playlistId, @RequestParam String songId) throws Exception {
        return playlistService.addSongToPlaylist(playlistId, songId);
    }

    @UtilityClass
    public static class Links {
        public static final String ADD_SONG_TO_PLAYLIST_URL = "/addSong";
    }
}

I use Postman to make requests. And after request I take this answer, which shows that song was added to playlist. https://i.sstatic.net/VdfbC.png

But as I said, if check playlist_song db, its nothing there. it means that my program doest correctly save many-to-many tables. https://i.sstatic.net/9lgW9.png

And in logs there are any exceptions.

So I can understand what is wrong. Hope somebody has an idea.


Solution

  • For SongEntity's @EqualsAndHashCode(onlyExplicitlyIncluded = true), you don't have any explicitly included annotations. You have a few listed to Exclude, but based on the docs at https://projectlombok.org/features/EqualsAndHashCode, it seems to require you explicitly mark them as Included when you use this flag.

    I'm not 100% sure what Lombok does when you put this flag at the class level but then don't explicitly include anything, but it seems like an invalid state and may be messing up your Equals and Hashcode and consequently messing up ability to track the items in the HashSet bucket that represents the songs that you're adding to.

    So I'd start by fixing this so your Equals/HashCode is correct. Which looks like you'd want to either remove the onlyExplicitlyIncluded=true settings altogether OR you actually add specific Includes.

    Here is a thread that talks about using explicit Includes if you want to stick with that route: How to use @EqualsAndHashCode With Include - Lombok

    Also, why do you have @Transient annotations on your relationships? That annotation usually tells the EntityManager to ignore the content it's attached to. In your case, if you're adding something to the Set but marking the Set as @Transient, then intuitively it should not affect in the Database. Suggest you remove those annotations where you want changes to objects in the relationships/sets to actually be reflected in the database.