Здравствуйте, уважаемые читатели! Те, кто разрабатывает 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

Комментарии (4)


  1. maxzh83
    16.12.2018 10:36

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

    Как я уже писал в предыдущей статье, если будете использовать Lombok, то не надо будет опускать геттеры и сеттеры, потому что их не будет.


    1. developerc Автор
      16.12.2018 15:40

      Да, в теории я это понимаю, и на гитхабе MapStruct с Lombok есть пример. Но попробовал, для моего задания у меня не получилось их состыковать.


  1. ris58h
    16.12.2018 11:05

    Горшочек, не вари.


    1. developerc Автор
      16.12.2018 15:42

      В смысле с этим MapStruct много магии? Согласен.