MapStruct nieskończona pętla (cykle)

MapStruct nieskończona pętla (cykle)
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

Cześć
Pisałem ostatnio pewną aplikację (cos takiego robiłem pierwszy raz, więc zawiera baardzo bardzo duzo złych praktyk ).

No więc składa się ona z backendu w Springu, który mi tam wystawia jakies "RESTowe" API (z restem ma niewiele wspólnego, no ale controllery zwracaja mi JSONy). Druga część aplikacji to frontend w javaFX. Pomyślałem sobie że fajnie byłoby aby dane między aplikacja kliencka, a serwerem były przesyłane za posrednictwem obiektów na wzór DTO. Wydzieliłem więc jeszcze jeden moduł aplikacji, z którego korzysta zarówno backend jak i frontend.

Tylko teraz w czym problem.... Widziałem tutaj na forum dyskusję, że @Entity z JPA/Hibernate , to tak naprawdę prawie DTO. Ja jednak chciałbym je zmapować na swoje DTO (nawet jesli to będzie na chwilę obecną praktycznie 1:1) i używać tych samych szablonów w obu aplikacjach (fx, spring). Dołączyłem do mapowania framework mapstruct.
Problem pojawia się jesli chcę mapować klasę, która jest w relacji wiele do jednego z inną klasą. Mapstruct wtedy popada w nieskończoną rekurencję, dostaję stackoverflow i po marzeniach.

Poniżej link do modelu bazy danych
https://www.vertabelo.com/blog/technical-articles/a-database-model-for-a-movie-theater-reservation-system

Problem dotyczy mapowania klasy Auditorium, która ma składową Seat. Seat zaś ma składową Auditorium.
Jak to obejść? Czy powinienem zmodyfikować swoje DTOski, tak aby nie miały cykli?

PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0
Kopiuj
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuditoriumDTO {

    private int id;
    private String name;
    private Integer rows;
    private Integer cols;
    private List<SeatDTO> seats;
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class SeatDTO {
    private int id;
    private int row;
    private int number;
    private boolean active;
    private AuditoriumDTO auditorium;

}

S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
1

Po co w SeatDTO referencja do AuditoriumDTO?


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
PI
w sumie masz rację, ona tam jest niepotrzebna, nigdy nie pobieram z api samego siedzenia, abym miał potrzebę wiedziec do jakiej sali nalezy :)
QB
  • Rejestracja:prawie 10 lat
  • Ostatnio:dzień
  • Lokalizacja:Lublin
  • Postów:171
1

Jeśli dodałeś referencję Auditorium do obiektu Seat, tylko po to, żeby wygodnie się do niego dostać, to musisz inaczej to rozwiązać. DTO to jednak coś innego niż JPA entities - w klasach Entity masz pola w obu, żeby pokazać jaka jest relacja między obiektami.

Głównym celem DTO jest dostarczanie klientom danych do widoków. Jeśli np. masz w apce widok, który wyświetla szczegóły Auditorium, wtedy sobie wydzielasz np. package rest.auditorium, a w nim DTOs tylko dla rest endpointa, który udostępnia dane dla tego widoku, np:

Kopiuj
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuditoriumDTO {
 
    private int id;
    private String name;
    private Integer rows;
    private Integer cols;
    private List<SeatDTO> seats;
}
 
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class SeatDTO {
    private int id;
    private int row;
    private int number;
}

i kontroler, który zwraca dane dla tego widoku.

Jeśli masz też widok, w którym oglądasz szczegóły siedzenia, to sobie wydzielasz package rest.seat, w którym też definiujesz tylko to co potrzebne dla tego widoku, np.

Kopiuj
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuditoriumDTO {
    private int id;
    private String name;
}
 
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class SeatDTO {
    private int id;
    private int row;
    private int number;
    private boolean active;
    private AuditoriumDTO auditorium;
}

I do tego jeszcze kontroler

Może się wydawać, że to trochę duplikowanie kodu ale każdy widok wygląda inaczej, może się zmieniać niezależnie od drugiego, więc korzystanie ze wspólnych DTO z reguły wiąże się z wyższym kosztem utrzymania takiego kodu. Podsumowując - masz 1 zestaw klas @Entity, ale DTOsy lepiej mieć osobne dla każdego z widoków. Jeśli chcesz współdzielić embedded DTO pomiędzy różnymi widokami - rób to ostrożnie.

edytowany 6x, ostatnio: qbns
PI
Świetnie wyjaśnione, dzięki. Wygląda trochę jak nadmiarowość kodu, ale tak jak mówisz, kod staje się łatwy do utrzymania dzięki tej nadmiarowości
QB
BTW. Jeśli chciałbyś udostępniać REST API w jakiś bardziej ustandaryzowany sposób, polecam zapoznać się z HAL i HATEOAS, które możesz zaimplementować z pomocą spring-hateoas - nie jest to jakoś super ważne, ale może pomóc w ogarnięciu bardziej skomplikowanych endpontów - np. gdy wiemy że klient mobilny będzie chciał skorzystać z endpointa, ale nie będzie potrzebował tak dużo danych jak klient desktopowy. Wtedy mobilny pobierając auditorium może dostać tylko odnośniki do seatów a desktopowy od razu wszystkie dane bo np. będzie wyświetlał mapę Audytorium ze wszystkimi seatami
QB
Anyway, i tak powyższe można po zwojemu rozwiązać - jeśli pracujesz nad projektem sam, to HATEOAS wydłużyłby czas developmentu, ale warto znać jak chcesz pacować nad czymś większym, z większą ilością osób - wtedy nie musisz opisywać ludziom wszystkich standardów, które sam wymyśliłeś, tylko dać linka do dokumentacji HATEOAS :D
PI
pomyślę o tym pomyślę :) Btw. widzę że też Lublin :D

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.