Trwałość Java z JPA i Hibernate, Część 1: Jednostki i relacje

Java Persistence API (JPA) to specyfikacja języka Java, która wypełnia lukę między relacyjnymi bazami danych a programowaniem obiektowym. Ten dwuczęściowy samouczek przedstawia JPA i wyjaśnia, w jaki sposób obiekty Java są modelowane jako jednostki JPA, w jaki sposób są definiowane relacje między jednostkami oraz jak używać JPA EntityManagerz wzorcem repozytorium w aplikacjach Java.

Zauważ, że ten samouczek używa Hibernacji jako dostawcy JPA. Większość koncepcji można rozszerzyć na inne struktury trwałości Java.

Co to jest JPA?

Zobacz „Co to jest JPA? Wprowadzenie do Java Persistence API”, aby dowiedzieć się więcej o ewolucji JPA i powiązanych platform, w tym EJB 3.0. i JDBC.

Relacje z obiektami w JPA

Relacyjne bazy danych istnieją jako środek do przechowywania danych programów od lat 70. Chociaż programiści mają dziś wiele alternatyw dla relacyjnej bazy danych, ten typ bazy danych jest skalowalny i dobrze rozumiany i nadal jest szeroko stosowany w tworzeniu oprogramowania na małą i dużą skalę.

Obiekty Java w kontekście relacyjnej bazy danych są definiowane jako jednostki . Jednostki są umieszczane w tabelach, w których zajmują kolumny i wiersze. Programiści używają kluczy obcych i łączą tabele do definiowania relacji między jednostkami - mianowicie relacji jeden do jednego, jeden do wielu i wiele do wielu. Możemy również używać SQL (Structured Query Language) do pobierania i interakcji z danymi w poszczególnych tabelach i wielu tabelach, używając ograniczeń klucza obcego. Model relacyjny jest płaski, ale programiści mogą pisać zapytania w celu pobierania danych i konstruowania obiektów na podstawie tych danych.

Niedopasowanie impedancji relacji z obiektem

Możesz być zaznajomiony z terminem niedopasowanie impedancji relacji z obiektem , które odnosi się do wyzwania związanego z mapowaniem obiektów danych do relacyjnej bazy danych. Ta niezgodność występuje, ponieważ projektowanie zorientowane obiektowo nie jest ograniczone do relacji jeden do jednego, jeden do wielu i wiele do wielu. Zamiast tego w projektowaniu obiektowym myślimy o obiektach, ich atrybutach i zachowaniu oraz o relacjach między obiektami. Dwa przykłady to hermetyzacja i dziedziczenie:

  • Jeśli obiekt zawiera inny obiekt, to definiujemy poprzez hermetyzacji -A ma-a związek.
  • Jeśli obiekt jest specjalizacją innego obiektu, definiujemy to poprzez dziedziczenie - jest to relacja.

Asocjacja, agregacja, kompozycja, abstrakcja, uogólnienie, realizacja i zależności to koncepcje programowania zorientowanego obiektowo, które mogą być trudne do odwzorowania na model relacyjny.

ORM: mapowanie obiektowo-relacyjne

Niedopasowanie między projektowaniem obiektowym a modelowaniem relacyjnych baz danych doprowadziło do powstania klasy narzędzi opracowanych specjalnie do mapowania obiektowo-relacyjnego (ORM). Narzędzia ORM, takie jak Hibernate, EclipseLink i iBatis, tłumaczą modele relacyjnych baz danych, w tym encje i ich relacje, na modele zorientowane obiektowo. Wiele z tych narzędzi istniało przed specyfikacją JPA, ale bez standardu ich funkcje zależały od dostawcy.

Po raz pierwszy wydany jako część EJB 3.0 w 2006 roku, Java Persistence API (JPA) oferuje standardowy sposób opisywania obiektów, dzięki czemu można je odwzorowywać i przechowywać w relacyjnej bazie danych. Specyfikacja definiuje również wspólną konstrukcję do interakcji z bazami danych. Posiadanie standardu ORM dla języka Java zapewnia spójność we wdrożeniach dostawców, a jednocześnie zapewnia elastyczność i dodatki. Na przykład, chociaż oryginalna specyfikacja JPA ma zastosowanie do relacyjnych baz danych, niektóre implementacje dostawców rozszerzyły JPA do użytku z bazami danych NoSQL.

Ewolucja WZP

Pierwsze wydanie JPA, wersja 1.0, zostało opublikowane w 2006 r. Za pośrednictwem Java Community Process (JCP) jako Java Specification Request (JSR) 220. Wersja 2.0 (JSR 317) została opublikowana w 2009 r., Wersja 2.1 (JSR 338) w 2013 r., a wersja 2.2 (wydanie poprawkowe JSR 338) została opublikowana w 2017 r. JPA 2.2 została wybrana do włączenia i dalszego rozwoju w Dżakarcie EE.

Pierwsze kroki z JPA

Java Persistence API to specyfikacja, a nie implementacja: definiuje typową abstrakcję, której możesz użyć w swoim kodzie do interakcji z produktami ORM. W tej sekcji omówiono niektóre z ważnych części specyfikacji JPA.

Dowiesz się, jak:

  • Zdefiniuj jednostki, pola i klucze podstawowe w bazie danych.
  • Utwórz relacje między jednostkami w bazie danych.
  • Pracuj z EntityManageri jego metodami.

Definiowanie jednostek

Aby zdefiniować jednostkę, musisz utworzyć klasę, do której zostanie przypisana @Entityadnotacja. @EntityAdnotacja jest adnotacja znacznik , który jest używany do odkrywania podmiotów utrzymujących. Na przykład, jeśli chcesz utworzyć księgę, powinieneś opisać ją następująco:

 @Entity public class Book { ... } 

Domyślnie ta jednostka zostanie zmapowana do Booktabeli, zgodnie z podaną nazwą klasy. Jeśli chcesz zmapować tę jednostkę do innej tabeli (i opcjonalnie określonego schematu), możesz użyć do tego @Tableadnotacji. Oto jak Bookzmapowałbyś klasę do tabeli BOOKS:

 @Entity @Table(name="BOOKS") public class Book { ... } 

Jeśli tabela BOOKS była w schemacie PUBLISHING, możesz dodać schemat do @Tableadnotacji:

 @Table(name="BOOKS", schema="PUBLISHING") 

Mapowanie pól do kolumn

Po zmapowaniu encji na tabelę następnym zadaniem jest zdefiniowanie jej pól. Pola są definiowane jako zmienne składowe w klasie, przy czym nazwa każdego pola jest odwzorowywana na nazwę kolumny w tabeli. Możesz zastąpić to domyślne mapowanie, używając @Columnadnotacji, jak pokazano poniżej:

 @Entity @Table(name="BOOKS") public class Book { private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

W tym przykładzie zaakceptowaliśmy domyślne mapowanie nameatrybutu, ale określiliśmy niestandardowe mapowanie dla isbnatrybutu. nameAtrybut zostanie odwzorowany do nazwy kolumny, ale isbnatrybut zostanie odwzorowany na kolumnę ISBN_NUMBER.

@ColumnAdnotacji pozwala nam określić dodatkowe właściwości pola / kolumny, w tym długości, czy jest pustych, czy to musi być wyjątkowy, jego precyzja i skala (jeśli jest to wartość dziesiętna), czy jest wprowadzany i uaktualniać, i tak dalej .

Określenie klucza podstawowego

Jednym z wymagań stawianych tabeli relacyjnej bazy danych jest to, że musi ona zawierać klucz podstawowy lub klucz, który jednoznacznie identyfikuje określony wiersz w bazie danych. W JPA używamy @Idadnotacji, aby wyznaczyć pole jako klucz podstawowy tabeli. Klucz podstawowy musi być typem pierwotnym Java, opakowaniem pierwotnym, takim jak Integerlub Long, a String, a Date, a BigIntegerlub a BigDecimal.

W tym przykładzie mapujemy idatrybut, którym jest an Integer, na kolumnę ID w tabeli BOOKS:

 @Entity @Table(name="BOOKS") public class Book { @Id private Integer id; private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

Możliwe jest również połączenie @Idadnotacji z @Columnadnotacją w celu nadpisania mapowania nazw kolumn klucza podstawowego.

Relacje między podmiotami

Teraz, gdy wiesz, jak zdefiniować jednostkę, przyjrzyjmy się, jak tworzyć relacje między jednostkami. JPA definiuje cztery adnotacje do definiowania jednostek:

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

Relacje jeden do jednego

@OneToOneAdnotacja jest używany do określenia relacji jeden do jednego między dwoma elementami. Na przykład możesz mieć Userencję, która zawiera nazwę użytkownika, adres e-mail i hasło, ale możesz chcieć zachować dodatkowe informacje o użytkowniku (takie jak wiek, płeć i ulubiony kolor) w oddzielnej UserProfileencji. @OneToOneAdnotacja ułatwia uszkodzi danych i podmioty w ten sposób.

Poniższa Userklasa ma jedną UserProfileinstancję. UserProfileMapy do jednej Userinstancji.

 @Entity public class User { @Id private Integer id; private String email; private String name; private String password; @OneToOne(mappedBy="user") private UserProfile profile; ... } 
 @Entity public class UserProfile { @Id private Integer id; private int age; private String gender; private String favoriteColor; @OneToOne private User user; ... } 

Dostawcę JPA zastosowania UserProfile„s userpola mapować UserProfiledo User. Mapowanie jest określone w mappedByatrybucie w @OneToOneadnotacji.

Relacje jeden do wielu i wiele do jednego

@OneToManyI @ManyToOneadnotacje ułatwienia obu stronach tego samego związku. Rozważmy przykład, w którym Bookmoże mieć tylko jedną Author, ale Authormoże mieć wiele książek. BookJednostka zdefiniować @ManyToOnerelację z Authora Authorjednostka określenia @OneToManyrelacji z Book.

 @Entity public class Book { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="AUTHOR_ID") private Author author; ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @OneToMany(mappedBy = "author") private List books = new ArrayList(); ... } 

W tym przypadku Authorklasa prowadzi listę wszystkich książek napisanych przez tego autora, a Bookklasa zachowuje odniesienie do swojego jednego autora. Ponadto @JoinColumnokreśla nazwę kolumny w Booktabeli, w której ma być przechowywany identyfikator Author.

Relacje wiele do wielu

Finally, the @ManyToMany annotation facilitates a many-to-many relationship between entities. Here's a case where a Book entity has multiple Authors:

 @Entity public class Book { @Id private Integer id; private String name; @ManyToMany @JoinTable(name="BOOK_AUTHORS", [email protected](name="BOOK_ID"), [email protected](name="AUTHOR_ID")) private Set authors = new HashSet(); ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @ManyToMany(mappedBy = "author") private Set books = new HashSet(); ... } 

In this example, we create a new table, BOOK_AUTHORS, with two columns: BOOK_ID and AUTHOR_ID. Using the joinColumns and inverseJoinColumns attributes tells your JPA framework how to map these classes in a many-to-many relationship. The @ManyToMany annotation in the Author class references the field in the Book class that manages the relationship; namely the authors property.

That's a quick demo for a fairly complex topic. We'll dive further into the @JoinTable and @JoinColumn annotations in the next article.

Working with the EntityManager

EntityManager is the class that performs database interactions in JPA. It is initialized through a configuration file named persistence.xml. This file is found in the META-INF folder in your CLASSPATH, which is typically packaged in your JAR or WAR file. The persistence.xml file contains:

  • The named "persistence unit," which specifies the persistence framework you're using, such as Hibernate or EclipseLink.
  • A collection of properties specifying how to connect to your database, as well as any customizations in the persistence framework.
  • A list of entity classes in your project.

Let's look at an example.

Configuring the EntityManager

First, we create an EntityManager using the EntityManagerFactory retrieved from the Persistence class:

 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books"); EntityManager entityManager = entityManagerFactory.createEntityManager(); 

In this case we've created an EntityManager that is connected to the "Books" persistence unit, which we've configured in the persistence.xml file.

The EntityManager class defines how our software will interact with the database through JPA entities. Here are some of the methods used by EntityManager:

  • find retrieves an entity by its primary key.
  • createQuery creates a Query instance that can be used to retrieve entities from the database.
  • createNamedQuery loads a Query that has been defined in a @NamedQuery annotation inside one of the persistence entities. Named queries provide a clean mechanism for centralizing JPA queries in the definition of the persistence class on which the query will execute.
  • getTransaction defines an EntityTransaction to use in your database interactions. Just like database transactions, you will typically begin the transaction, perform your operations, and then either commit or rollback your transaction. The getTransaction() method lets you access this behavior at the level of the EntityManager, rather than the database.
  • merge() adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using merge(), objects are not managed.
  • persist adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using persist(), objects are managed.
  • refresh refreshes the state of the current entity from the database.
  • flush synchronizes the state of the persistence context with the database.

Nie martw się integracją wszystkich tych metod naraz. Poznasz je, pracując bezpośrednio z EntityManager, co zrobimy więcej w następnej sekcji.