
Здравствуйте, уважаемые читатели! Те, кто разрабатывает 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
 
          
 
maxzh83
Как я уже писал в предыдущей статье, если будете использовать Lombok, то не надо будет опускать геттеры и сеттеры, потому что их не будет.
developerc Автор
Да, в теории я это понимаю, и на гитхабе MapStruct с Lombok есть пример. Но попробовал, для моего задания у меня не получилось их состыковать.