Здравствуйте, уважаемые читатели! Те, кто разрабатывает 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 есть пример. Но попробовал, для моего задания у меня не получилось их состыковать.