2

Good morning, nice to greet you.

I am new to Spring Boot recently, and I am working with a REST API, which is basically a playlist with songs, basically the REST API should have the following structure. A playlist can have many songs:

{
    "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
        }
    ]
}

Currently this is how I have my entities configured

Entity SONGS

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

Controller

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

}

My class 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);
    }
    
}

My Repository

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

    Optional<PlayList> findById(Long Id);
    
}

However, I have infinite recursion problem when I try to get a playlist with the get method:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (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.

This is due to the playList field found in the Songs entity, when trying to get the playlist with the songs, I get an array that becomes recursive and not the id of the playList, which is what I want to get, the reality is that I get something like this:

enter image description here

I can solve by applying a JsonIgnore in the field, however this affects me that when I go to save a playlist, I cannot pass the idPlayList field to each song to make sure that each song is saved with its respective idPlayList

I think the best thing would be to build a DTO here that helps me to have the relationship of the 2 tables, however, I don't know how I would do it in a way that allows me, when executing a get from the playList, to obtain only the idPlayList, and when saving power pass the idPlayList in each song that is going to be saved with a playlist, or if there is another way in which I could store a playlist with its songs and have them stored directly, since currently I have to assign by BD to each song the idPlayList

Cesar Justo
  • 707
  • 2
  • 11
  • 35

1 Answers1

1
  1. Add spring.jpa.open-in-view=false to the application.properties to have LazyInitializationException. It will help to build good queries. What is this spring.jpa.open-in-view=true property in Spring Boot?

  2. Load PlayList with songs using JPQL query with LEFT JOIN FETCH to fetch songs with the list. So songs will be already loaded and playList in the song will be lazy. @EntityGraph can be used also.

    select list from PlayList list left join fetch list.songs where list.id = :listId
    
  3. There is Hibernate5Module in Jackson. It allow to omit lazy associations. So Jackson will not touch playList in the song. Configure Jackson to omit lazy-loading attributes in Spring Boot

  4. To build something like a real system. You can add PlayListResponse, SongResponse classes and map entities to those classes on the service level. Probably you will not need Hibernate5Module in this case. But better to experiment with this module too.

  5. Enable database logging in application.properties with

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

Check transactions and SQL in the logs carefully. Don't allow Hibernate to produce unnecessary queries.

How to save the songs with a list

Add this method to the PlayList

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

Add songs to the list using addSong() method.

Save PlayList. All the songs of a list will be saved by Hibernate with the correct list id, because there is cascade = CascadeType.ALL in the @OneToMany.

v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • Thanks for your answer, could you give me an example of LEFT JOIN FETCH? if you have it to map the playlist with their songs. – Cesar Justo Feb 19 '21 at 22:30
  • I have been reading @EntityGraph and it is very good because it allows me to improve the performance in the query, however, I still do not understand how I could load (insert) a playlist with its songs, since the id of the playList is autogenerated and autoincremented by which I do not know how to associate it so that the songs are saved with their id_playList value and thus remain associated to the playList – Cesar Justo Feb 23 '21 at 01:42
  • Thanks, I'm going to test the changes and I'll tell you – Cesar Justo Feb 23 '21 at 16:11
  • What's up friend? Could you help me with the following question that is related to the Delete method of a song within a PlayList https://stackoverflow.com/questions/67527711/delete-object-from-an-entity-with-a-onetomany-relationship-between-both-using-jp – Cesar Justo May 14 '21 at 13:01