Как стать автором
Обновить

Spring Boot решение задачи с ManyToMany

Время на прочтение3 мин
Количество просмотров35K


Статья адресуется тебе, бредущему во мраке ночи. Тебе, чей путь освещают лишь одинокие светлячки. В общем, программисту, изучающему Spring Boot и отношение ManyToMany в Hibernate.

Делал тестовое задание на отвлеченную музыкальную тему: написать сервис для хранения данных о музыкантах, песнях, альбомах, используя Java, Spring, Hibernate. Частью задания было создание классов «Композиции» и «Исполнители». Композицию могут исполнять многие исполнители, и исполнитель может исполнять многие композиции. Типичное bidirectional отношение ManyToMany.

На курсах Khasang рассказывали, как избежать зацикливания rest запросов на Spring с помощью DTO класса, но Spring Boot это другая история. На русскоязычных сайтах видел ответы типа «Да это все элементарно», но без конкретных пояснений. Приведу пример решения этой задачи. Полный код размещен на гитхабе, ссылка внизу.

Сначала создадим Entity: People и SongPlayers. Геттеры и сеттеры опускаю для краткости.

@Entity
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String human;
    //несколько людей играют в одной группе
    @ManyToOne(cascade = CascadeType.ALL)
    private RockGroups rockGroups;
    //разные люди исполняют разные композиции
    @ManyToMany(mappedBy = "songInstrumentalist",fetch = FetchType.EAGER)
    private List<SongPlayers> songItems;
    public People(){}
    public People(long id, String human){
        this.id = id;
        this.human = human;
    }
//. . . . . . . . .
}

@Entity
public class SongPlayers {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String song;
    //у композиции один композитор
    private String composer;
    // и один автор стихов
    private String poet;
    //песня содержится в альбоме
    private String album;
    //и много исполнителей
    //исполнители могут исполнять разные песни
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<People> songInstrumentalist;
//. . . . . . . . .
}

Затем создаем Repository интерфейсы для класса People

@Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
    @Query("select h from People h where h.human=?1")
    List<People> searchByHuman(String human);
    List<People> findPeopleByHuman(String human);
}

И для класса SongPlayers

@Repository
public interface SongPlayersRepository extends JpaRepository<SongPlayers, Long> {
    List<SongPlayers> findSongPlayersBySong(String song);
    List<SongPlayers> findSongPlayersByComposer(String composer);
    List<SongPlayers> findSongPlayersByPoet(String poet);
}

Аннотация Repository расширяет аннотацию Component, что позволяет имплементированный класс делать бином и соответственно аутовайрить.

Расширение интерфейса JpaRepository позволяет производить нужные CRUD операции без дополнительного описания и другие полезные вещи.

Теперь нужно создать DTO классы для Entity People и SongPlayers. Здесь приведу только PeopleDTO что бы не загромождать статью. Геттеры и сеттеры снова опустил.

public class PeopleDTO {
    private long id;
    private String human;
    private RockGroups rockGroups;
    private List<SongPlayersDTO> songPlayersList;
    public List<PeopleDTO> getPeopleDTOList(List<People> peopleList){
        List<PeopleDTO> peopleDTOList = new ArrayList<>();
        for (People people : peopleList){
            songPlayersList = new ArrayList<>();
            PeopleDTO peopleDTO = new PeopleDTO();
            peopleDTO.setId(people.getId());
            peopleDTO.setHuman(people.getHuman());
            peopleDTO.setRockGroups(people.getRockGroups());
            for (SongPlayers songPlayers : people.getSongItems()){
                SongPlayersDTO songPlayersDTO = new SongPlayersDTO();
                songPlayersDTO.setId(songPlayers.getId());
                songPlayersDTO.setSong(songPlayers.getSong());
                songPlayersDTO.setPoet(songPlayers.getPoet());
                songPlayersDTO.setComposer(songPlayers.getComposer());
                songPlayersDTO.setAlbum(songPlayers.getAlbum());
                songPlayersList.add(songPlayersDTO);
            }
            peopleDTO.setSongPlayersList(songPlayersList);
            peopleDTOList.add(peopleDTO);
        }
        return peopleDTOList;
    }
//. . . . . . . . .
}

По аналогии создается и SongPlayersDTO класс. Выбираем нужные поля для отображения в rest — ответе.

Создадим контроллер для пипла. Внимание, следите за руками!

@RestController
@RequestMapping("/people")
public class PeopleController {
    @Autowired
    private PeopleRepository repository;
    @GetMapping("/all")
    public List<PeopleDTO> getAllPeople(){
        PeopleDTO peopleDTO = new PeopleDTO();
        return peopleDTO.getPeopleDTOList(repository.findAll());
    }
//. . . . . . .
}

Ага, скажете Вы. Так работать не будет. А где имплементация интерфейса PeopleRepository?
Будет работать, класс PeopleRepository создается на лету! Просто фантастика.

А теперь сравните количество создаваемых классов и интерфейсов для идентичного проекта на Spring и Spring Boot
классы в проекте Spring классы в Spring Boot

Желаю всем успешного программирования. Жду ваших комментариев.


Используемая литература:

  • Felipe Gutierrez Pro Spring Boot
  • Craig Walls Spring in action 5-th edition
Теги:
Хабы:
Всего голосов 10: ↑6 и ↓4+2
Комментарии12

Публикации