REST mini service problem z GET + zwracanie linków.

0

Witam, otóż próbuje sobie stworzyć mini web service RESTowy, który jest połączony jest z baza danych mysql.
Mam dwie klasy book i author. Oraz dwie 4 metody w kontrolerze:


@Controller
@RequestMapping("api")
public class BookController {

    private BookService bookService;
    private AuthorService authorService;

    @Autowired
    public void setBookService(BookService bookService) {
        this.bookService = bookService;
    }

    @Autowired
    public void setAuthorService(AuthorService authorService) {
        this.authorService = authorService;
    }

    @RequestMapping(method = RequestMethod.GET, value = "/book")
    public @ResponseBody
    List<Book> getAllBooks() {
        return bookService.listAllBook();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/book/{id}")
    public @ResponseBody
    Book getBookById(@PathVariable Long id) {
        return bookService.getBookById(id);
    }


    @RequestMapping(method = RequestMethod.GET, value = "/author")
    public @ResponseBody
    List<Author> getAllAuthors() {
        return authorService.listAllAuthors();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/author/{id}")
    public @ResponseBody
    Author getAuthorById(@PathVariable Long id) {
        return authorService.getAuthorById(id);
    }
}

Problem polega na tym, że metody działają prócz ten ostatniej, która zwraca błąd 500. Nie rozumiem za bardzo czemu. W załączeniu zrzuty jak to wygląda.
Drugie pytanie moje brzmi: otóż z tego co się dowiedziałem (jeżeli się mylę to proszę o wyprowadzenie mnie z błędu), że lepszym rozwiązaniem jest to, że np. kiedy zwracana jest lista książek. Każda książka ma autora, który z kolei ma imie, nazwisko itd. To dobrym wyjściem jest to, żeby nie był zwracany cały autor a link do niego. Jak to zrobić? Znalazłem coś takiego: http://www.baeldung.com/spring-hateoas-tutorial ale nie za bardzo wiem jak to zaimplementować, gdzie umieścić tworzenie linku w którym miejscu powinno się to znajdować.
https://github.com/MatDevCode

0
  1. Zwracanie @Entity do widoku i w ogóle żonglowanie klasami @Entity to proszenie sie o guza.
  2. Nie potrafisz po ludzku skopiować stacktrace? Masz w nim wyraźnie napisane czego jackson nie potrafi sam zserializować. No ale po co czytać komunikaty blędu...
0
Shalom napisał(a):
  1. Zwracanie @Entity do widoku i w ogóle żonglowanie klasami @Entity to proszenie sie o guza.
  2. Nie potrafisz po ludzku skopiować stacktrace? Masz w nim wyraźnie napisane czego jackson nie potrafi sam zserializować. No ale po co czytać komunikaty blędu...

Ad1. Powinna być jeszcze jakaś warstwa pośrednia? Jakieś konwertowanie?
Ad2. Eh, czytałem komunikat i nie rozumiem dlaczego przy jednej klasie (Book) się wszystko udaje a przy drugiej już nie. Wcześniej nie dodawałem żadnych rzeczy konfiguracyjnych.

1

Tak powinnny byc obiekty DTO zwracane. Chodzi o to żeby
1)Mieć niezależne API od encji
2)Nie narobić sobie problemów z transakcjami zwłaszcza jak korzystarz z JpaTransactionManagera :)

0
scibi92 napisał(a):

Tak powinnny byc obiekty DTO zwracane. Chodzi o to żeby
1)Mieć niezależne API od encji
2)Nie narobić sobie problemów z transakcjami zwłaszcza jak korzystarz z JpaTransactionManagera :)

 private Book convertToDTO(Book model)
    {
        Book dto = new Book();
        dto.setId(model.getId());
       ...
        return dto;
    }

Coś takiego w klasie BookService czy tworzyć nową klasę?

0

@mat90 ta i jeszcze tą metodę miej w jakiejś klasie z @Transactional i zobaczysz jakie cuda się zaczną dziać z bazą danych :D Jak ci nagle obiekty będą znikać i sie pojawiać... ;]

0

Czyli tak, mam klasę np. Book:

@Entity
public class Book extends ResourceSupport {

    @Id
    @GeneratedValue
    Long book_id;

    @ManyToOne
    Author author;

    String title;
    String isbn;
    String description;
//gettery i settery

Tworzę klasę BookDTO:

public class BookDTO {

    Long book_id;
    AuthorDTO authorDTO;
    String title;
    String isbn;
    String description;
//getter i settery

Następnie klasę:

public class BookConverter {

    public static Book convertToBook(BookDTO bookDTO) {
        Book book = new Book();
        book.setBook_id(bookDTO.getBook_id());
        book.setTitle(bookDTO.getTitle());
        book.setDescription(bookDTO.getDescription());
        book.setIsbn(bookDTO.getIsbn());
        return book;
    }

    public static BookDTO convertToBookDTO(Book book) {
        BookDTO bookDTO = new BookDTO();
        bookDTO.setBook_id(book.getBook_id());
        bookDTO.setTitle(book.getTitle());
        bookDTO.setDescription(book.getDescription());
        bookDTO.setDescription(book.getIsbn());
        return bookDTO;
    }

Używając tej klasy robię operacje w klasie BookServiceImpl?

0

Zaraz mnie @jarekr000000 albo @Koziołek zjedzą, ale generalnie jeśli używasz JPA to tak jest najbezpieczniej właśnie. Są ewentualnie jakieś automatyczne generatory mapperów i takie sam, ale koncepcyjnie tak to wygląda mniej więcej. Przy czym jeśli postanowisz być fanem DDD (jak @somekind) to na dobrą sprawę to DTO będziesz zwracał tylko z kontrolera do widoku, a w warstwie logiki (czyli w swoich serwisach i poniżej) będziesz korzystał z obiektów domenowych... ;]

0

@scibi92 @Shalom Po co mu to DTO?

Trochę się podepnę pod temat, ale po co generalnie stosować DTO? Czyli pomijając przypadki wyjątkowe, czyli gdy np. chcemy z jakiegoś powodu oszczędzać na przesyle albo przy jakiejś ciekawszej agregacji danych itd.

1)Mieć niezależne API od encji
2)Nie narobić sobie problemów z transakcjami zwłaszcza jak korzystarz z JpaTransactionManagera :)

Jakie problemy? Przecież to bezpośrednie wyrzucenie do JSONa. Dla mnie to trochę wygląda jak dodatkowy narzut który się po prostu zazwyczaj nie przydaje a dodatkowo trzeba utrzymywać więcej kodu.

0

Nie potrzebujesz kontrolera i tego całego badziewia dookoła. Problem polega na tym, że jak odczytujesz coś z bazy, to ORM nie zwraca ci obiektu klasy takiej jak encja, ale proxy, które ogarnia duperele typu lazy loading. Do serializera trafia proxy i serializer się gubi, bo nie myli typ proxy z typem, który jest proxowany.

Rozwiązania są dwa.

Pierwsze moim zdaniem paskudniejsze, to użycie jakiegoś mechanizmu pomiędzy encją, a JSONem. Paradoksalnie całkiem nieźle sprawdzają się tutaj zwykłe adnotacje jacksonowe, które odpowiednio skompensują głupotę serializera i podpowiedzą mu co ma robić. Problem z tym rozwiązaniem polega na tym, że możesz wpaść w annotation hell gdzie w jednej klasie na polu będziesz miał adnotacje z kilku narzędzi.

Drugie, to zastosowanie wzorca wrocławskiego, czyli „Encja na twarz i pchasz” i użycie @RepositoryRestResource, która wystawi ci dostęp do bazy danych po interfejsie RESTowym. I jedynie co trzeba zrobić to trochę pomiziać konfigurację security, by miała sens -> https://docs.spring.io/spring-data/rest/docs/current/reference/html/#security.secured

0

Wywaliłem controllera i wszystko inne. W zasadzie została klasa domain Book i Repository z adnotacją @RepositoryRestResource i mam ten sam efekt co wcześniej.
Z jednej strony super ale z drugiej - nie za łatwo? Jestem na początku nauki. Pewnie konfiguracja by to miało sens to już inna sprawa. Ale zastanawiam się to w końcu jak to ma być :)

3

Tylko taka uwaga że z tym @RepositoryRestResource to ma sens tylko jeśli aplikacja na zero logiki a ty chcesz po prostu pchać RESTem dane z bazy. To wtedy faktycznie nie ma sensu sie bawić w jakieś serwisy i kontrolery i inne duperele. @Fedaykin zabawa obiektami @Entity to igranie z ogiem bo jeśli coś w takim obiekcie zmienisz w obrębie transakcji/sesji to automatycznie spowoduje zmianę w bazie danych. Trywialny przykład z życia wzięty który widziałem na własne oczy -> ktoś chciał wesoło wysyłać restem czy tam do widoku dane z @Entity ale nie chciał wysyłać wszystkich danych (np. dowiązanych kolekcji itd) żeby nie wysyłać niepotrzebnie połowy bazy, więc przed wysłaniem danych nullował sobie te kolekcje. Pech chciał ze robił to w serwisie który był objęty @Transactional co powodowało że automatycznie te dowiązane kolekcje były... usuwane z bazy danych :)

4
Shalom napisał(a):

Przy czym jeśli postanowisz być fanem DDD (jak @somekind) to na dobrą sprawę to DTO będziesz zwracał tylko z kontrolera do widoku, a w warstwie logiki (czyli w swoich serwisach i poniżej) będziesz korzystał z obiektów domenowych... ;]

Nie no, bez jaj. :P Ja jestem prześmiewcą DDD, bo to generalnie nigdzie nie zaimplementowany buzzword.
Tyle tylko, że jestem zdania, że jak już idziemy w DDD, to idziemy na całego, a nie, że mamy kontrolery wołające repozytoria, a wszędzie latają "encje", które są tak naprawdę modelami anemicznymi. W efekcie czego jesteśmy w połowie w ciąży i jakieś nóżki nam wystają z d**y, wygląda to co najmniej niesmacznie.

Fedaykin napisał(a):

Trochę się podepnę pod temat, ale po co generalnie stosować DTO? Czyli pomijając przypadki wyjątkowe, czyli gdy np. chcemy z jakiegoś powodu oszczędzać na przesyle albo przy jakiejś ciekawszej agregacji danych itd.

Manipulacja na danych to moim zdaniem przypadek typowy, rzadko kiedy chcemy zwracać dokładnie to, co mamy w bazie. Poza tym bezpieczniej jest izolować wnętrzności od świata zewnętrznego, wtedy możemy zmieniać wewnętrzną implementację i refaktoryzować bez obaw, że przypadkiem zepsujemy kontrakt API.

Jakie problemy? Przecież to bezpośrednie wyrzucenie do JSONa. Dla mnie to trochę wygląda jak dodatkowy narzut który się po prostu zazwyczaj nie przydaje a dodatkowo trzeba utrzymywać więcej kodu.

Taka oszczędność się szybko zemści, gdy jednak okaże się, że trzeba zmienić format wyjściowy, a pozostawić strukturę bazy. Albo gdy okaże się, że przez magię orma, prosty request wyciąga pół bazy. Myślę, że są tysiące miejsc, w których można mądrzej zaoszczędzić na kodzie, w tym przypadku np. stosując konstruktor zamiast dwóch oddzielnych setterów mogących zostawić obiekt w jakimś dziwnym stanie.

TL;DR dla przeświadczonych o tym, że nie potrafię pisać o Javie bez wspomnienia o C#
Java ssie! C# lebszy!!!!1111oneoneone

0

@Shalom:
Sprytny kolega :). Ale czy wtedy nie mamy poważniejszego problemu - stosowania 'magicznych' narzędzi? W sensie, jak ktoś nie wie o tym to i tak prędzej czy później wpadnie na minę.
Czyli proponowanym podejściem byłoby doklejanie DTO 'z góry' i jednoczesna minimalizacja obecności 'instancji' @Entity w systemie - co sprowadza się do możliwie najszybszego przemapowania na DTO. Bo jeżeli nie przemapujemy na wstępie to nadal @Entity mogą nam latać tu i tam. Czy takie podejście to nie jest taka trochę próba ominięcia 'mniej intuicyjnego' zachowania JPA/Hibernate?

somekind napisał(a):
Fedaykin napisał(a):

Jakie problemy? Przecież to bezpośrednie wyrzucenie do JSONa. Dla mnie to trochę wygląda jak dodatkowy narzut który się po prostu zazwyczaj nie przydaje a dodatkowo trzeba utrzymywać więcej kodu.

Taka oszczędność się szybko zemści, gdy jednak okaże się, że trzeba zmienić format wyjściowy, a pozostawić strukturę bazy. Albo gdy okaże się, że przez magię orma, prosty request wyciąga pół bazy. Myślę, że są tysiące miejsc, w których można mądrzej zaoszczędzić na kodzie, w tym przypadku np. stosując konstruktor zamiast dwóch oddzielnych setterów mogących zostawić obiekt w jakimś dziwnym stanie.

No właśnie, wraz ze wzrostem skomplikowania ich przydatność rośnie ale czy nie lepiej wstrzymać się do momentu aż potrzeba ich wprowadzenia zaistnieje? Czasami jest to oczywiste od początku, a czasami niekoniecznie i wtedy mam wrażenie, że metoda 'encja na twarz i pchasz' może się okazać zupełnie wystarczająca przez zaskakująco długi czas.

5

@Fedaykin z tego kolegi się tak nie smiej bo to wcale nie jest taka dziwna rzecz. Może dam bardziej życiowy przykład żebyś zobaczył że sam byś się mógł tak wpakować ;) Wyobraź sobie że chcesz wystawić restem informacje o userach w bazie. Nic łatwiejszego, ot pobierasz sobie listę obiektów Entity User i walisz do RESTa. No tylko ze tabela User ma hasło :( no a głupio byłoby hasło wystawiać tym restem, no to zamiast zmapować to na jakieś DTO po prostu znullujemy to hasło przed wysłaniem! Ale że jesteś dobrym programistą to wiesz ze nie pakuje sie logiki w kontroler, więc logika idzie do serwisu. Jesteś też dobrym programistą i wiesz ze transakcje zakłada sie na całą metodę biznesową żeby łatwo było ją można rollbackować "w całości". No i kończy się tak że kasujesz wszystkim userom hasła :)

Jeśli chodzi o narzędzia to nie ma wyboru. Jak działa kompilator pewnie za bardzo nie wiesz, jak JVM optymalizuje bajtkod w locie pewnie też nie. Którym narzędziom nie chcesz ufać? Wybór jest taki ze albo klepiesz kod jak zwierze za pomocą hexedytora w kodzie maszynowym, albo używasz narzędzi i możesz się skupić na realnych problemach biznesowych a nie na duperelach w stylu jak zmapować obiekt do jsona albo jak zmapować dane z bazy do obiektu. Coś za coś.

Ekspertem nie jestem, może coś na ten temat powiedzą @Koziołek @Krolik albo @jarekr000000 ale ja generalnie sugeruje w ogóle nie żonglować w kodzie obiektami @Entity tylko mapować je na jakieś obiekty które faktycznie są ci potrzebne, czy to będą obiekty domenowe czy choćby te DTO.

0

Żonglowanie @Entiry z JPA jest oczywiście niebezpieczne - a takie przykłady jak podał @Shalom mi się normalnie zdażyły.
(Już kojarzę, że chyba Appfuse + Spring Roo generował taki kod (encja na twarz) domyślnie).
Ale zamiast głupiego przypisywania z Entity w jakieś DTO, BO, PStro ( co się końcu często nudne robi ) polecam olać JPA. W takim JOOQ można zrobić wszyskto na obiektach immutable, a te można w zasadzie bez kłopotu wszędzie przekazywać (czasem z jakimiś proxy/adapterami).
W JPA też trochę się da zrobić na immutablach - ale to walenie kotka.

0

Pozwolę sobie podpiąć kolejne pytanie do tego wątku. Mam Autora który ma kilka książek i tutaj przy GET wyskakuje:

timestamp	1501786628683
status	500
error	"Internal Server Error"
exception	"java.lang.StackOverflowError"
message	"No message available"
path	"/api/author/2"

Ktoś mi powie dlaczego tak jest?

0

Bo twoja metoda kontrolera która obsługuje /api/id jest zrypana i wywala się ze stackoverflow exception? o_O Tzn pewnie wywala się coś tam niżej co jest wołane, ale to szczegół. Może po prostu przeczytasz sobie jednak treść całego wyjątku który pojawia sie na konsoli? No i laskawie napisz jakieś unit testy do tego kodu jak człowiek to takich cudów nie będzie.

0

jak wywaliłem wszystko konwertery itd. Działałem na czystych encjach to jak tylko nie usunę pola List<Book> w klasie Author to nie mogę uzyskać odpowiedzi z serwera. Tylko, że teraz wywala się komunikat: ```
SyntaxError: JSON.parse: end of data after property value in object at line 1 column 152304 of the JSON data

I jak mam go rozumieć, że JSON nie wyświetli zagnieżdżonej listy? I muszę to jakoś "przerobić"?
0

jak wywaliłem wszystkie konwertery itd. Działałem na czystych encjach to jak tylko nie usunę pola List<Book> w klasie Author to nie mogę uzyskać odpowiedzi z serwera. Tylko, że teraz wywala się komunikat:

SyntaxError: JSON.parse: end of data after property value in object at line 1 column 152304 of the JSON data

I jak mam go rozumieć, że JSON nie wyświetli zagnieżdżonej listy? I muszę to jakoś "przerobić"?

EDIT już chyba rozwiązałem sprawę: faktycznie zapętliło się na maksa wszystko: najpierw książka, która ma autora a ten ma książki, które mają autorów i tak w kółko.

1
Fedaykin napisał(a):

No właśnie, wraz ze wzrostem skomplikowania ich przydatność rośnie ale czy nie lepiej wstrzymać się do momentu aż potrzeba ich wprowadzenia zaistnieje?

Parafrazując: czy lepiej zaprojektować łazienkę w nowym domu od razu, czy wstrzymać się z tym dopiero do czasu aż się zamieszka i będzie się chciało umyć?

Czasami jest to oczywiste od początku, a czasami niekoniecznie i wtedy mam wrażenie, że metoda 'encja na twarz i pchasz' może się okazać zupełnie wystarczająca przez zaskakująco długi czas.

Takie coś może przejść w jakichś pomocniczych narzędziach, np. administracyjnej aplikacji do zarządzania jakąś konfiguracją składowaną w paru tabelach w bazie. Jeszcze nie widziałem biznesowej aplikacji, w której widok mapowałby się 1:1 do modelu w bazie. Co nie znaczy, że niektórzy nie próbowali tak to implementować, tylko zazwyczaj kończyło się to pobieraniem sporych ilości nadmiarowych danych, a potem ukrywaniem ich po stronie aplikacji. A potem święte wojny, bo "ormy są niewydajne!".

A dla mnie po prostu transformowanie, filtrowanie, projektowanie i sortowanie danych powinno się odbywać jak najbliżej ich źródła, a nie w GUI. Bo GUI może nawet nie być, a aplikacja i tak powinna działać.

0

Jak nie ma logiki to mozna uzywac spring rest repositories.

A odnosnie DDD jak to nigdzie nie jest uzywane...
To sa firmy w Polsce w ktorych nie przejdziesz rekrutacji za tworzenie anemicznych mapperow/konwerterow.

0

Znowu poruszę ten temat. Ktoś mi wytłumaczy (na prostych klasach ale z zależnościami, może być na tych co mam w kodzie :D) , jak powinien wyglądać model DTO? Czy to ma być w zasadzie kopia bliźniacza klasy domonowej? Mam nadzieję, że ktoś będzie łaskawy i zajrzy w kod. Drugi problem polega na tym, że w moim obecnym kodzie nie mogę dodać książki w ten sposób:

{
        "title": "title5",
        "isbn": "isbn5",
        "description": "opis5",
        "author_id": 2,
        "fistName": "Adam",
        "lastName": "Mickiewicz"
    }

Chodzi o to, żebym mógł dodać książkę z autorem a nie musiał dodawać tych dwóch encji osobno.
Co do DTO to już kombinował na ileś sposobów i naprawdę nie wiem jak to powinno wyglądać. Jak to powinno koncepcyjnie wyglądać, zwłaszcza w przypadku gdzie są relację. Proszę bardzo o pomoc.

https://github.com/MatDevCode

0

A mozesz konkretniej w czym tkwi problem?

W tym jsonie wedlug mnie powinienes raczej przesylac tylko authorId jesli po prostu zapisujesz ksiazke do autora co juz istnieje.

Ale jesli chcesz dodawac ksiazke i autora na raz to zrobilbym Dto z dwoma obiektami w srodku, dla bookDto i authorDto. Z nich tworzyl encje i zapisywal do bazy.

Nie robilbym rzeczy w stylu createOrUpdate tylko robil osobne endpointy.

0

Btw. Takie cos wyglada dziwnie:

convertBookDTOtoAuthorDTO

0

Osobne w sensie, że osobno update i create?

Problem polega na tym, że otrzymuje odpowiedź 200 i:

{
    "book_id": null,
    "title": "test",
    "isbn": "test",
    "description": "test",
    "author": {
        "author_Id": 2,
        "firstName": "Adam",
        "lastName": "Mickiewcz",
        "list": null
    }
}

Ale w bazie danych nic nie przybyło.
Próbuje to zrobić tak, że BookDTO ma imie i nazwisko autora i nawet ID (żeby było łatwiej) i konwerterami to przekształcić na encję.. TO słuszna droga? I czy to nie powinno się nazywać ModelDTO? Bo z Bookiem na tyle wspólnego z autorem.

1

Skoro zamierzasz stworzyc ksiazke i autora... I nie ma tego jeszcze w bazie to skad bierzesz ich ID?

Zakladam, ze sam to oprogramowales, wiec to, ze leci 200 to niewiele znaczy. Testy masz?

Id autora powinienes przeslac razem z ksiazka jesli juz ten autor istnieje i zapisac relacje.

Konwencja jest raczej taka, ze z POSTem tworzysz a PUTem aktualizujesz.

Z tego co widze to w bookservice wstrzykujesz repozytoria ale ich nie uzywasz przy save... Tylko uzywasz konwertera i zwracasz wynik konwersji. Jesli nie uzyjesz repozytorium to nie licz na to, ze zapiszesz cos do bazy. Jak zapiszesz do bazy to powinienes dostac od niej ID...

0

Ok powiedzmy, że działa. Trochę źle to wygląda raczej ale zaraz zrobię porządek czy coś. Co do testów to one powinny wyglądać na zasadzie, że wysyłam wiadomość i porównuje z tym co jest w bazie danych? Czy konwertery też testować? Tzn samo konwertowanie z obiektu na obiekt?

1

@mat90:
"Masz rację ale tylko tak zadziała. Tzn kwestia czy chodzi Ci o to, że w ogóle konwertuje obiekt książki na autora czy o to, że na autora DTO a nie bezpośrednio na Autora?"

Jakbys mial Dto, które trzymałoby BookDto oraz AuthorDto, to moglbys te obiekty konwertowac niezaleznie.

Patrząc na Twoje zaawansowanie, to testowałbym wszystko, dla wprawy.

0

@mat90: tak

1 użytkowników online, w tym zalogowanych: 0, gości: 1