JPA i Hibernate #10 - Relacja One-to-one

JPA #10 – Relacja One-to-one

Po kilku artykułach poświęconych tematyce CRUD czas na relacje one-to-one i one-to-many. Przybliżą nam one trochę bardziej skomplikowany świat obiektów i opisujących ich danych. Poznamy jak Hibernate ułatwia nam tworzenie powiązań między tabelami.

W poprzednich artykułach poznaliśmy działanie CRUD, czyli Create, Read, Update i Delete. Przykładem był prosty obiekt gość (Guest), o dwóch danych imię (name) i wieku (age). Na nasze szczęście, grafy obiektów bywają bardziej urozmaicone i nie wkradnie się tu nuda. Czas odrobinę zahaczyć o tą kwestię.

Rozbudowanie projektu App

Dodaj kolejną klasę Javy o nazwie Reservation.

Rezerwacje, prócz wielu możliwych danych, zawsze są przypisane do jakiegoś gościa.

W tej klasie utwórz pole id i pole guest wraz z konstruktorem.

Zrób z klasy encję, bo oczywiście chcemy zapisu tych danych do bazy danych.

Dodaj adnotacje @Id i @GeneratedValue, o automatycznym generowaniu wartości.

Kod naszej klasy Reservation powinien prezentować się w ten sposób:

package pl.clockworkjava.hotelreservation.jpa;

import javax.persistence.*;
import java.util.List;

@Entity
public class Reservation {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private Guest guest;

    @OneToMany
    private List<Guest> guests;

    public Reservation(List<Guest> guest) {
        this.guests = guest;
    }
}

Po wykonaniu tego kodu zobaczymy wyjątek – i słusznie:

Konsola aplikacji – MappingException

Nie można określić jakim typem prostym ma być Guest.

Warto mieć w pamięci, że JPA mapuje w ten sposób: String – typ tekstowy, int – typ liczbowy w relacyjnych bazach danych.

Obiekt nie jest typem w relacyjnych bazach danych i stąd nasz błąd. Musimy go podzielić na wartości proste.

Na poziomie Java chcemy, żeby Rezerwacja posiadała Gościa.

Na poziomie relacyjnej bazy danych musimy określić, że mamy Rezerwację i ona wskazuje na x wiersz w tabeli Guest (Gościa). Właśnie to wskazywanie nazywamy relacją.

Relacja one-to-one

Relacja jeden do jednego czyli one-to-one to najprostszy typ relacji. Wskazuje, że dokładnie jednej encji odpowiada dokładnie jedna inna encja. Stosujemy adnotację @OneToOne.

Wskazujemy aby encja Reservation wskazywała na inną encję Guest w poniższy sposób.

package pl.clockworkjava.hotelreservation.jpa;

import javax.persistence.*;

@Entity
public class Reservation {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @OneToOne
    private Guest guest;

    public Reservation(Guest guest) {
        this.guest = guest;
    }
}

Odpal program i przekonaj się co to zmieni i jakie tabele stworzy nam dzięki temu Hibernate.

U mnie pojawiły się takie informacje:

Konsola aplikacji – informacje o stworzonych tabelach przez Hibernate

W tabeli Reservation możemy zaobserwować powiązanie z tabelą Guest poprzez guest_id. Dodatkowo linijkę niżej widać dodany klucz obcy, czyli wszystko jest ok.

Przykład relacji one-to-one na grafie

Zajmijmy się przykładem relacji na podstawie naszych dwóch klas – Reservation i Guest.

Graf opisujący relację one-to-one na przykładzie Reservation i Guest

Tabela Reservation ma jeden wpis i są to pola obowiązkowe ID(2) oraz Guest_ID(1). Guest_ID jest ID obiektu w Tabeli Guest – wskazuje nam na odpowiedni wiersz przypisany do danej rezerwacji.

Tabela Guest zawiera jeden wpis z polami ID (1), NAME (Paweł) i AGE (34).

Uwaga: W H2 sekwencja nadawania ID jest wspólna dla wszystkich tabel.

JPA utworzyło nam tą relację automatycznie. Wystarczyło dodać adnotacje @OneToOne.

Nowe repozytorium dla Reservation

W głównej klasie App dodajmy kolejne repozytorium dla klasy Reservation. Tworzymy od razu tą klasę. Dodajemy w kodzie tworzenie rezerwacji i implementujemy tą metodę w właśnie stworzonej klasie ReservationRepository

package pl.clockworkjava.hotelreservation.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class ReservationRepository {

    private EntityManager em;

    public ReservationRepository(EntityManager em) {
        this.em = em;
    }

    public void createReservation(Guest guests) {

        Reservation r = new Reservation(guests);

        EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        em.persist(r);
        transaction.commit();

    }
}

Chcemy zapisać to w bazie danych więc potrzebujemy Entity Managera.

Wstrzykiwanie Entity Managera

Zamiast tworzyć kolejnego bytu użyjemy wstrzykiwania zależności. Oznacza to, że wyciągniemy sobie EM do glównej klasy tak, aby inne mogły korzystać.

Wycinamy z GuestRepository dwie linijki kodu Entity Managera i przenosimy je do App powyżej metody main.

package pl.clockworkjava.hotelreservation.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class App {

    private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("thePersistenceUnit");
    private static EntityManager em  = factory.createEntityManager();

    public static void main(String[] args) {
        GuestRepository guestRepository = new GuestRepository(em);
        ReservationRepository reservationRepository = new ReservationRepository(em);
        guestRepository.createNewGuest("Paweł", 34);
        Guest guest = guestRepository.findById(1l);
        reservationRepository.createReservation(guest);
    }
}

Od razu zmieniamy argument w GuestRepository i ReservationRepository na em. Czyli oznacza to, że każde z repozytorium będzie miało swoje pole Entity Managera

W ten sposób wstrzyknęliśmy do naszych repozytoriów Entity Managera.

Tworzenie rezerwacji

Czas opakować tworzenie rezerwacji w transakcję.

W metodzie createReservation() utwórz sposób dodawania nowej rezerwacji.

Rezerwacja wskazuje na wcześniej stworzonego gościa (guest).

Do utworzenia używamy metody em.persist().

Na końcu wypisz na ekranie informację. Ja sugeruję “Created reservation for guest” i odpowiednie ID.

Nasz kod powinien wyglądać tak:

package pl.clockworkjava.hotelreservation.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class ReservationRepository {

    private EntityManager em;

    public ReservationRepository(EntityManager em) {
        this.em = em;
    }

    public void createReservation(Guest guests) {

        Reservation r = new Reservation(guests);

        EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        em.persist(r);
        transaction.commit();

    }
}

Podsumowanie

Na ten moment w programie możemy:

  • utworzyć nowego gościa
  • odszukać gościa po ID
  • utworzyć nową rezerwację
  • rezerwacja wskazuje na ID gościa

Za pomocą prostego kodu możemy stworzyć relacje. Hibernate za nas automatycznie wykona powiązania między odpowiednimi tabelami. Dzięki temu nie musimy na tym etapie w to wnikać. I to jest tym cudownym plusem pracy z nim.

By być na bieżąco i mieć realny wpływ na tematykę tworzonych artykułów zapraszam do dołączenia do mojego newslettera.


Informacje oparte zostały o materiał darmowego kursu wprowadzającego w świat JPA i Hibernate “Fundamenty JPA i Hibernate” dostępnego w formie wideo na platformie ClockworkJava.

Pozostałe artykuły z cyklu:

  1. Czym jest persystencja?
  2. ORM, JPA i Hibernate
  3. Podstawowe elementy
  4. Konfiguracja projektu
  5. Pierwsza encja
  6. Create
  7. Read
  8. Update
  9. Delete
  10. Relacja One-to-One
  11. Relacja One-to-Many

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *