MapStruct решение задачи с ManyToMany



    Здравствуйте, уважаемые читатели! Те, кто разрабатывает Web приложения на Java с использованием фреймворка Spring, те кто комментирует эти приложения и просто интересующиеся.

    В предыдущей статье «Spring Boot решение задачи с ManyToMany»

    я приводил пример выполненного тестового приложения, в котором между двумя классами имеется bidirectional отношение ManyToMany. В статье приводился пример решения проблемы зацикливания при получении rest ответа с помощью использования DTO класса. Читатели в комментариях предложили использовать для решения проблемы зацикливания библиотеку MapStruct.

    Ознакомившись с документацией я убедился, что это действительно сильная вещь, с помощью которой можно решать достаточно сложные задачи перемещения данных между объектами. MapStruct решает проблему зацикливания в том числе.

    В данной статье я приведу пример решения той же задачи в виде приложения Spring Boot с использованием библиотеки MapStruct. Исходный код доступен на Гитхабе

    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 и SongPlayers

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

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

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

    public class PeopleDTO {
        private long id;
        private String human;
        private RockGroups rockGroups;
        private List<SongPlayersDTO> songPlayersList;
    // . . . . .
    
    }

    public class SongPlayersDTO {
        private long id;
        private String song;
        private String composer;
        private String poet;
        private String album;
    // . . . . .
    }

    Для описания правила передачи данных из исходного объекта в DTO объект и, при необходимости, обратно создаем маппер интерфейсы для каждого класса, для которого требуется защита от зацикливания. Здесь приведу PeopleMapper и SongPlayersMapper

    @Mapper(uses = SongPlayersMapper.class)
    public interface PeopleMapper {
        PeopleMapper PEOPLE_MAPPER = Mappers.getMapper(PeopleMapper.class);
        @Mapping(source = "songItems", target = "songPlayersList")
        PeopleDTO fromPeople(People people);
    }

    @Mapper/*(uses = {PeopleMapper.class})*/
    public interface SongPlayersMapper {
        SongPlayersMapper SONG_PLAYERS_MAPPER = Mappers.getMapper(SongPlayersMapper.class);
        SongPlayersDTO fromSongPlayers(SongPlayers songPlayers);
        @InheritInverseConfiguration
        SongPlayers toSongPlayers(SongPlayersDTO songPlayersDTO);
    }

    В папке Service создадим интерфейсы и имплементацию сервисных классов, в которые поместим методы получения данных (приведу для People).

    public interface PeopleService {
        List<PeopleDTO> getAllPeople();
        PeopleDTO getPeopleById(long id);
        People addPeople(People people);
        void delPeople(long id);
        ResponseEntity<Object> updPeople(People people, long id);
        List<RockGroups> getByHuman(String human);
        List<String> getSongByHuman(String human);
    }

    @Service("peopleservice")
    public class PeopleServiceImpl implements PeopleService {
        @Autowired
        private PeopleRepository repository;
        @Override
        public List<PeopleDTO> getAllPeople() {
            List<PeopleDTO> peopleDTOList = new ArrayList<>();
            List<People> peopleList = repository.findAll();
            for (People people : peopleList){
                peopleDTOList.add(PeopleMapper.PEOPLE_MAPPER.fromPeople(people));
            }
            return peopleDTOList;
        }
    // . . . . .
    }

    В контроллерах эти методы будем соответственно применять (опять только для People)

    @RestController
    @RequestMapping("/people")
    public class PeopleController {
        @Autowired
        private PeopleServiceImpl service;
        @GetMapping("/all")
        public List<PeopleDTO> getAllPeople(){
            return service.getAllPeople();
        }
    // . . . . .
    }

    Из приведенного решения задачи зацикливания при отношении ManyToMany могу сказать, что рабочим является и вариант с DTO классами из предыдущей статьи и вариант с библиотекой MapStruct из настоящей статьи. По сравнению с предыдущим вариантом класс DTO значительно упростился, но прибавились Mapper интерфейсы. В общем любой способ можно использовать, для простых случаев я бы склонился к первому варианту.

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

    Ссылка на предыдущую статью.

    Ссылка на проект на Гитхабе.

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

    • Felipe Gutierrez Pro Spring Boot
    • Craig Walls Spring in action 5-th edition
    Share post

    Comments 4

      0
      Геттеры и сеттеры опускаю.

      Как я уже писал в предыдущей статье, если будете использовать Lombok, то не надо будет опускать геттеры и сеттеры, потому что их не будет.
        0
        Да, в теории я это понимаю, и на гитхабе MapStruct с Lombok есть пример. Но попробовал, для моего задания у меня не получилось их состыковать.
        +1
        Горшочек, не вари.
          0
          В смысле с этим MapStruct много магии? Согласен.

        Only users with full accounts can post comments. Log in, please.