Aplikacja serwer - klient a relacje w bazie

0

Uczę się Spring Boot, Rest, JPA z użyciem biblioteki Jersey.

Napisałem Rest Service - serwer, który wystawia jedną encję Book(id, name, publishDate) w postaci json, wystawia też całego CRUDa.

Teraz chce zrobić klienta, który pobierałby jsony z linku. Chcę mieć relację OneToMany miedzy Person (id, firstname, lastname) a Book, jedna osoba może mieć wiele książek. I później wystawić z klienta coś w stylu PersonAndBooks(id_person, firstname, lastname, bookList(id, name, publishDate). I nie wiem w sumie jak to powinno wyglądać...

W istniejącym projekcie serwera utworzyć klienta, dopisać encję Person i zrobić miedzy nimi relację (i wrzucić np. do encji book -> person_id)? Tylko jaki w sumie miałoby to sens? Bo po co byłby mi wtedy ten klient? A jak klient w osobnym projekcie to musiałbym zmapować encję Book, ale jak wtedy zrobić relację?
A może jeszcze inaczej to powinno być zrobione? I powinien być jakiś projekt z częściami wspólnymi?

Trochę mało konkretny temat wyszedł ale proszę o pomoc.

0

Do encji Person dodaj relację do Book:

 @OneToMany(targetEntity = Book.class)
 @JoinColumn(name = "id")
 private List<Book> books;

Poczytaj też o FetchType i o JOIN FETCH

0

Ok, chciałem tak zrobić i to zadziała mając klienta i serwer w jednym projekcie.

A mając w osobnych projektach? To muszę zmapować jsona i też zrobić to samo?

0

Tak, z RESTa przeze mnie napisanego. I mnie zastanawia jak to powinno być najlepiej napisane.
Przepraszam, gubię się ;)

Znalazłem coś takiego:
jak wyglądają encje po stronie klienta
"Utwórz sobie mały osobny moduł w projekcie nazwij go Model i umieść tam encje. Niech część serwerowa jak i kliencka będzie od niego zależna (classpath). Przesyłasz sobie wtedy te obiekty. To jest pierwsza opcja.

Druga to mapowanie obiektów encji na JSON przed wysłaniem i po stronie klienta deserializacjia ich do nowych klas. Taka wersja sprawdza się gdy klient nie potrzebuje wszystkich informacji z encji."

To pierwsze to chyba lepsza opcja. Ale może chciałbym spróbować ten drugi sposób.
Rest wystawia: localhost8080/books , localhost8080/books/1, localhost8080/books/2... itp. w postaci json
encja Book(id, name, publishDate)

Robię clienta, mapuję sobie w nim encję Book , tworzę encję Person i robię w w kliencie relację OneToMany między nimi?
Trochę nie rozumiem na jakiej zasadzie miałoby to zadziałać.

Ok, według mnie po prostu Rest-Serwer powinien również zwracać jakaś informację o Person.
Np. person_id przypisanego do danej książki. Wtedy aplikacja kliencka będzie mogła sobie na wystawionych danych operować.
Czy tak?

0

Doszedłem do wniosku, że to chyba powinny być 2 aplikacje server-side... a klient to swoją drogą.

0

Szczerze, nie widzę potrzeby tworzenia dodatkowej aplikacji server-side. Nie bardzo też rozumiem, jaki masz problem z klientem.

Skupmy się na tym problemie:

Teraz chce zrobić klienta, który pobierałby jsony z linku. Chcę mieć relację OneToMany miedzy Person (id, firstname, lastname) a Book, jedna osoba może mieć wiele książek. I później wystawić z klienta coś w stylu PersonAndBooks(id_person, firstname, lastname, bookList(id, name, publishDate). I nie wiem w sumie jak to powinno wyglądać...

Jak rozumiem potrzebujesz JSONa, który będzie miał mniej więcej taką postać:

[
  {
    "id": "1",
    "name": "Camille Kent",
    "books": [
      {
        "id": 1,
        "author": "Valarie Mullins",
        "title": "Ullamco exercitation deserunt deserunt dolore eu laborum tempor do excepteur qui."
      },
      {
        "id": 2,
        "author": "Charmaine Jensen",
        "title": "Magna adipisicing irure ex laborum aliquip cillum in cupidatat anim."
      }
    ]
  }
]

Masz kilka sposobów na zbudowanie encji. Możesz automatycznie pobierać osobę i książki, możesz też pobierać tylko osoby, a ich książki tylko wtedy, kiedy potrzebujesz.

Pierwszy przypadek, kiedy zawsze zwracasz osobę z powiązanymi książkami wyglądałby tak:

@NamedQuery(name = "Person.findByPersonId", query = "SELECT p FROM Person p WHERE p. personId = :personId"),
public class Person implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "person_id")
	private Integer personId;
	@Column(name = "name")
	private String name;
	@OneToMany(targetEntity = Book.class, fetch = FetchType.EAGER)
	@JoinColumn(name = "id")
	private List<Book> books;

Załóżmy, że API będzie miało na przykład taką postać:

    @GET
    @Path("person/{id}")
    @Produces({"application/xml", "application/json"})
    public Response personWithBooksREST() {
        //... dodaj EntityManager i obsługę błędów

        Person person = em.createNamedQuery("Person.findByPersonId").setParameter("personId",1).getSingleResult();
        return Response.ok(new Gson().toJson(person)).build();
    }

Powyższe rozwiązanie zwróci JSONa, który będzie przedstawiał osobę i powiązane z nią książki. Musisz mieć jednak na uwadze, że kiedy będziesz chciał pobrać wszystkie osoby, aplikacja zwróci wszystkie osoby i ich powiązane książki (sam musisz ocenić, czy potrzebujesz zawsze wszystkich danych, czy potrzebujesz czasem np tylko listy osób, bez książek).

Kolejna opcja, kiedy aplikacja zwróci osobę i jej książki, ale tylko na wyraźne życzenie:
Po pierwsze mała zmiana w definicji encji:

@OneToMany(targetEntity = Book.class) //usuwamy FetchType.EAGER, teraz aplikacja użyje domyślnego ustawienia, czyli FetchType.LAZY
@JoinColumn(name = "id")
private List<Book> books;

Do tego musimy napisać odpowiednie zapytanie, które powie aplikacji, że chcemy, aby do osoby zostały dołączone książki:

@NamedQuery(name = "Person.getPersonWithBooks", query = "SELECT p FROM Person p JOIN FETCH p.books WHERE p. personId = : personId"),

Plus wywołanie w API:

Person person = em.createNamedQuery("Person.getPersonWithBooks").setParameter("personId",1).getSingleResult();

Powyższe to tylko dwa przykładowe sposoby na rozwiązanie przedstawionego problemu. JPA daje spore możliwości wystawienia danych w odpowiednim formacie, więc postaraj się udostępnić taki zakres i format danych, jaki akurat jest potrzebny.

dodanie znacznika <code class="java"> - @furious programming

0

Dzięki.
Myślę, że problem mam/miałem głównie taki, że nie zrozumiałem treści "zadania"/przykładu. Zrozumiałem początkowo, że ta część z "Person" to ma być aplikacja kliencka. Pomieszało mi się. Stąd nieporozumienie.

Gwoli ścisłości... rozumiem, że sugerujesz by rozbudować aplikacje server-side o encje Person i relacje między encjami i wystawić odpowiednio skonfigurowane rest api.

A na końcu jest klient, który wysyła requesty.

0

Tak, w Twoim projekcie będą dwie encje: Person i Book. Person będzie miała relację do Book (OneToMany). Jeśli chcesz mieć dwukierunkową relację, musisz dodać powiązanie z Book do Person. Do tego zbuduj odpowiednie zapytania.

Klient wysyła tylko żądania typu GET, PUT, POST, czy DELETE, w odpowiedzi dostaje JSONa (jeśli coś jest zwracane).

0

Bardzo dziękuje za pomoc.
Ogarnąłem sobie tę relację. Trochę inaczej niż napisałaś, ale przecież nie o to chodzi, żeby było tak samo ;) z paroma problemami, ale generalnie działa.
Klienta też dało radę napisać, jakiegoś najprostszego.

Ale będę musiał sobie poczytać więcej o tych adnotacjach. fetchach, joinach itp.
thx.

A jeszcze jedno pytanie.

A gdybym chciał zrobić 2 aplikacje server-side, jedną z informacjami o Book a drugą z info o Person, z różnymi źródłami danych.
To jak to można połączyć w jedno?

Jak zrobić coś takiego

  • Jeden service wystawia to: Book(id, name, publishDate, person_id) (no chyba, że jakimś sposobem nie potrzebuje person_id wystawiac) - i dziala na swojej bazie danych
  • Drugi service ma encje Person (id_person, firstname, lastname), która tez dziala na wlasnej bazie

Chce oba services skomunikowac i wystawic cos takiego z drugiego service:
PersonAndBooks(id_person, firstname, lastname, bookList(id, name, publishDate)

Jakas wskazowka?

help

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