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