Java-Persistenz mit JPA und Hibernate, Teil 1: Entitäten und Beziehungen
Die Java Persistence API (JPA) ist eine Java-Spezifikation, die die Lücke zwischen relationalen Datenbanken und objektorientierter Programmierung schließt. In diesem zweiteiligen Lernprogramm wird JPA vorgestellt und erläutert, wie Java-Objekte als JPA-Entitäten modelliert werden, wie Entitätsbeziehungen definiert werden und wie JPAs EntityManager
mit dem Repository-Muster in Ihren Java-Anwendungen verwendet werden.
Beachten Sie, dass in diesem Lernprogramm Hibernate als JPA-Anbieter verwendet wird. Die meisten Konzepte können auf andere Java-Persistenz-Frameworks erweitert werden.
Was ist JPA?
Weitere Informationen zur Entwicklung von JPA und verwandten Frameworks, einschließlich EJB 3.0, finden Sie unter "Was ist JPA? Einführung in die Java Persistence API". und JDBC.
Objektbeziehungen in JPA
Relationale Datenbanken existieren seit den 1970er Jahren als Mittel zum Speichern von Programmdaten. Während Entwickler heutzutage viele Alternativen zur relationalen Datenbank haben, ist diese Art von Datenbank skalierbar und gut verstanden und wird in der Softwareentwicklung im kleinen und großen Maßstab immer noch häufig verwendet.
Java-Objekte in einem relationalen Datenbankkontext werden als Entitäten definiert . Entitäten werden in Tabellen platziert, in denen sie Spalten und Zeilen belegen. Programmierer verwenden Fremdschlüssel und Verknüpfungstabellen , um die Beziehungen zwischen Entitäten zu definieren - nämlich Eins-zu-Eins-, Eins-zu-Viele- und Viele-zu-Viele-Beziehungen. Wir können auch SQL (Structured Query Language) verwenden, um Daten in einzelnen Tabellen und über mehrere Tabellen hinweg mithilfe von Fremdschlüsseleinschränkungen abzurufen und mit ihnen zu interagieren. Das relationale Modell ist flach, aber Entwickler können Abfragen schreiben, um Daten abzurufen und Objekte aus diesen Daten zu erstellen.
Nicht übereinstimmende Impedanz der Objektbeziehungen
Möglicherweise kennen Sie den Begriff Objekt-Beziehungs-Impedanz-Nichtübereinstimmung , der sich auf die Herausforderung bezieht, Datenobjekte einer relationalen Datenbank zuzuordnen. Diese Nichtübereinstimmung tritt auf, weil das objektorientierte Design nicht auf Eins-zu-Eins-, Eins-zu-Viele- und Viele-zu-Viele-Beziehungen beschränkt ist. Stattdessen denken wir beim objektorientierten Design an Objekte, ihre Attribute und ihr Verhalten sowie an die Beziehung zwischen Objekten. Zwei Beispiele sind Kapselung und Vererbung:
- Wenn ein Objekt ein anderes Objekt enthält, definieren wir dies durch Einkapselung --A -a hat Beziehung.
- Wenn ein Objekt eine Spezialisierung eines anderen Objekts ist, definieren wir dies durch Vererbung --an ist-ein - Beziehung.
Assoziation, Aggregation, Komposition, Abstraktion, Generalisierung, Realisierung und Abhängigkeiten sind objektorientierte Programmierkonzepte, deren Zuordnung zu einem relationalen Modell schwierig sein kann.
ORM: Objektrelationale Zuordnung
Die Nichtübereinstimmung zwischen objektorientiertem Design und relationaler Datenbankmodellierung hat zu einer Klasse von Werkzeugen geführt, die speziell für das objektrelationale Mapping (ORM) entwickelt wurden. ORM-Tools wie Hibernate, EclipseLink und iBatis übersetzen relationale Datenbankmodelle, einschließlich Entitäten und ihrer Beziehungen, in objektorientierte Modelle. Viele dieser Tools existierten vor der JPA-Spezifikation, aber ohne Standard waren ihre Funktionen herstellerabhängig.
Die Java Persistence API (JPA) wurde erstmals 2006 als Teil von EJB 3.0 veröffentlicht und bietet eine Standardmethode zum Kommentieren von Objekten, damit diese zugeordnet und in einer relationalen Datenbank gespeichert werden können. Die Spezifikation definiert auch ein allgemeines Konstrukt für die Interaktion mit Datenbanken. Ein ORM-Standard für Java sorgt für Konsistenz bei der Implementierung von Anbietern und ermöglicht gleichzeitig Flexibilität und Add-Ons. Während die ursprüngliche JPA-Spezifikation auf relationale Datenbanken anwendbar ist, haben einige Herstellerimplementierungen JPA für die Verwendung mit NoSQL-Datenbanken erweitert.
Entwicklung von JPA
Die erste Version von JPA, Version 1.0, wurde 2006 über den Java Community Process (JCP) als Java Specification Request (JSR) 220 veröffentlicht. Version 2.0 (JSR 317) wurde 2009 veröffentlicht, Version 2.1 (JSR 338) 2013; und Version 2.2 (eine Wartungsversion von JSR 338) wurde 2017 veröffentlicht. JPA 2.2 wurde für die Aufnahme und Weiterentwicklung in Jakarta EE ausgewählt.
Erste Schritte mit JPA
Die Java Persistence API ist eine Spezifikation, keine Implementierung: Sie definiert eine allgemeine Abstraktion, die Sie in Ihrem Code für die Interaktion mit ORM-Produkten verwenden können. In diesem Abschnitt werden einige wichtige Teile der JPA-Spezifikation behandelt.
Sie lernen:
- Definieren Sie Entitäten, Felder und Primärschlüssel in der Datenbank.
- Erstellen Sie Beziehungen zwischen Entitäten in der Datenbank.
- Arbeiten Sie mit dem
EntityManager
und seinen Methoden.
Entitäten definieren
Um eine Entität zu definieren, müssen Sie eine Klasse erstellen, die mit der @Entity
Annotation versehen ist. Die @Entity
Annotation ist eine Marker-Annotation , mit der persistente Entitäten erkannt werden. Wenn Sie beispielsweise eine Buchentität erstellen möchten, kommentieren Sie diese wie folgt:
@Entity public class Book { ... }
Standardmäßig wird diese Entität der Book
Tabelle zugeordnet, wie durch den angegebenen Klassennamen bestimmt. Wenn Sie diese Entität einer anderen Tabelle (und optional einem bestimmten Schema) zuordnen möchten, können Sie dazu die @Table
Anmerkung verwenden. So würden Sie die Book
Klasse einer BOOKS-Tabelle zuordnen:
@Entity @Table(name="BOOKS") public class Book { ... }
Wenn sich die Tabelle BOOKS im Schema PUBLISHING befand, können Sie das Schema zur @Table
Anmerkung hinzufügen :
@Table(name="BOOKS", schema="PUBLISHING")
Zuordnen von Feldern zu Spalten
Wenn die Entität einer Tabelle zugeordnet ist, besteht Ihre nächste Aufgabe darin, ihre Felder zu definieren. Felder werden als Elementvariablen in der Klasse definiert, wobei der Name jedes Felds einem Spaltennamen in der Tabelle zugeordnet wird. Sie können diese Standardzuordnung mithilfe der @Column
Anmerkung überschreiben , wie hier gezeigt:
@Entity @Table(name="BOOKS") public class Book { private String name; @Column(name="ISBN_NUMBER") private String isbn; ... }
In diesem Beispiel haben wir die Standardzuordnung für das name
Attribut akzeptiert , aber eine benutzerdefinierte Zuordnung für das isbn
Attribut angegeben. Das name
Attribut wird der Namensspalte zugeordnet , das isbn
Attribut jedoch der Spalte ISBN_NUMBER.
Mit der @Column
Anmerkung können wir zusätzliche Eigenschaften des Felds / der Spalte definieren, einschließlich der Länge, ob es nullwertfähig ist, ob es eindeutig sein muss, seiner Genauigkeit und Skalierung (wenn es sich um einen Dezimalwert handelt), ob es einfügbar und aktualisierbar ist und so weiter .
Angabe des Primärschlüssels
Eine der Anforderungen für eine relationale Datenbanktabelle besteht darin, dass sie einen Primärschlüssel oder einen Schlüssel enthalten muss , der eine bestimmte Zeile in der Datenbank eindeutig identifiziert. In JPA verwenden wir die @Id
Annotation, um ein Feld als Primärschlüssel der Tabelle festzulegen. Der Primärschlüssel muss ein primitiver Java-Typ, ein primitiver Wrapper wie Integer
oder Long
, a String
, a Date
, a BigInteger
oder a sein BigDecimal
.
In diesem Beispiel id
ordnen wir das Attribut, das ein Integer
ist, der ID-Spalte in der BOOKS-Tabelle zu:
@Entity @Table(name="BOOKS") public class Book { @Id private Integer id; private String name; @Column(name="ISBN_NUMBER") private String isbn; ... }
Es ist auch möglich, die @Id
Annotation mit der @Column
Annotation zu kombinieren, um die Spaltennamenzuordnung des Primärschlüssels zu überschreiben.
Beziehungen zwischen Entitäten
Nachdem Sie nun wissen, wie Sie eine Entität definieren, schauen wir uns an, wie Sie Beziehungen zwischen Entitäten erstellen. JPA definiert vier Anmerkungen zum Definieren von Entitäten:
@OneToOne
@OneToMany
@ManyToOne
@ManyToMany
Eins-zu-eins-Beziehungen
Die @OneToOne
Anmerkung wird verwendet, um eine Eins-zu-Eins-Beziehung zwischen zwei Entitäten zu definieren. Möglicherweise verfügen Sie über eine User
Entität, die den Namen, die E-Mail-Adresse und das Kennwort eines Benutzers enthält. Möglicherweise möchten Sie jedoch zusätzliche Informationen zu einem Benutzer (z. B. Alter, Geschlecht und bevorzugte Farbe) in einer separaten UserProfile
Entität verwalten. Die @OneToOne
Anmerkung erleichtert das Aufteilen Ihrer Daten und Entitäten auf diese Weise.
Die folgende User
Klasse hat eine einzelne UserProfile
Instanz. Die UserProfile
Zuordnungen zu einer einzelnen User
Instanz.
@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; ... }
Die PPV - Anbieter verwendet UserProfile
‚s user
Feld abzubilden UserProfile
zu User
. Die Zuordnung wird im mappedBy
Attribut in der @OneToOne
Anmerkung angegeben.
Eins-zu-viele und viele-zu-eins-Beziehungen
Die @OneToMany
und @ManyToOne
Anmerkungen erleichtern beide Seiten derselben Beziehung. Stellen Sie sich ein Beispiel vor, in dem a Book
nur eines haben kann Author
, aber Author
möglicherweise viele Bücher. Die Book
Entität würde eine @ManyToOne
Beziehung mit definieren Author
und die Author
Entität würde eine @OneToMany
Beziehung mit definieren 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(); ... }
In diesem Fall führt die Author
Klasse eine Liste aller von diesem Autor geschriebenen Bücher, und die Book
Klasse führt einen Verweis auf ihren einzelnen Autor. Darüber hinaus @JoinColumn
gibt das den Namen der Spalte in der Book
Tabelle an, in der die ID des gespeichert werden soll Author
.
Viele-zu-viele-Beziehungen
Finally, the @ManyToMany
annotation facilitates a many-to-many relationship between entities. Here's a case where a Book
entity has multiple Author
s:
@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 aQuery
instance that can be used to retrieve entities from the database.createNamedQuery
loads aQuery
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 anEntityTransaction
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. ThegetTransaction()
method lets you access this behavior at the level of theEntityManager
, 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 usingmerge()
, 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 usingpersist()
, 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.
Machen Sie sich keine Sorgen, wenn Sie alle diese Methoden gleichzeitig integrieren. Sie lernen sie kennen, indem Sie direkt mit dem EntityManager
arbeiten. Weitere Informationen hierzu finden Sie im nächsten Abschnitt.