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 EntityManagermit 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 EntityManagerund seinen Methoden.

Entitäten definieren

Um eine Entität zu definieren, müssen Sie eine Klasse erstellen, die mit der @EntityAnnotation versehen ist. Die @EntityAnnotation 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 BookTabelle 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 @TableAnmerkung verwenden. So würden Sie die BookKlasse 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 @TableAnmerkung 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 @ColumnAnmerkung ü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 nameAttribut akzeptiert , aber eine benutzerdefinierte Zuordnung für das isbnAttribut angegeben. Das nameAttribut wird der Namensspalte zugeordnet , das isbnAttribut jedoch der Spalte ISBN_NUMBER.

Mit der @ColumnAnmerkung 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 @IdAnnotation, um ein Feld als Primärschlüssel der Tabelle festzulegen. Der Primärschlüssel muss ein primitiver Java-Typ, ein primitiver Wrapper wie Integeroder Long, a String, a Date, a BigIntegeroder a sein BigDecimal.

In diesem Beispiel idordnen wir das Attribut, das ein Integerist, 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 @IdAnnotation mit der @ColumnAnnotation 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 @OneToOneAnmerkung wird verwendet, um eine Eins-zu-Eins-Beziehung zwischen zwei Entitäten zu definieren. Möglicherweise verfügen Sie über eine UserEntitä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 UserProfileEntität verwalten. Die @OneToOneAnmerkung erleichtert das Aufteilen Ihrer Daten und Entitäten auf diese Weise.

Die folgende UserKlasse hat eine einzelne UserProfileInstanz. Die UserProfileZuordnungen zu einer einzelnen UserInstanz.

 @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 userFeld abzubilden UserProfilezu User. Die Zuordnung wird im mappedByAttribut in der @OneToOneAnmerkung angegeben.

Eins-zu-viele und viele-zu-eins-Beziehungen

Die @OneToManyund @ManyToOneAnmerkungen erleichtern beide Seiten derselben Beziehung. Stellen Sie sich ein Beispiel vor, in dem a Booknur eines haben kann Author, aber Authormöglicherweise viele Bücher. Die BookEntität würde eine @ManyToOneBeziehung mit definieren Authorund die AuthorEntität würde eine @OneToManyBeziehung 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 AuthorKlasse eine Liste aller von diesem Autor geschriebenen Bücher, und die BookKlasse führt einen Verweis auf ihren einzelnen Autor. Darüber hinaus @JoinColumngibt das den Namen der Spalte in der BookTabelle 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 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.

Machen Sie sich keine Sorgen, wenn Sie alle diese Methoden gleichzeitig integrieren. Sie lernen sie kennen, indem Sie direkt mit dem EntityManagerarbeiten. Weitere Informationen hierzu finden Sie im nächsten Abschnitt.