Java-persistentie met JPA en Hibernate, deel 1: entiteiten en relaties

De Java Persistence API (JPA) is een Java-specificatie die de kloof overbrugt tussen relationele databases en objectgeoriënteerd programmeren. Deze tweedelige tutorial introduceert JPA en legt uit hoe Java-objecten worden gemodelleerd als JPA-entiteiten, hoe entiteitsrelaties worden gedefinieerd en hoe u JPA's kunt gebruiken EntityManagermet het Repository-patroon in uw Java-applicaties.

Merk op dat deze tutorial Hibernate gebruikt als de JPA-provider. De meeste concepten kunnen worden uitgebreid naar andere Java-persistentiekaders.

Wat is JPA?

Zie "Wat is JPA? Inleiding tot de Java Persistence API" voor meer informatie over de evolutie van JPA en gerelateerde frameworks, waaronder EJB 3.0. en JDBC.

Objectrelaties in JPA

Relationele databases bestaan ​​al sinds de jaren zeventig als middel voor het opslaan van programmagegevens. Hoewel ontwikkelaars tegenwoordig veel alternatieven hebben voor de relationele database, is dit type database schaalbaar en goed begrepen, en wordt het nog steeds veel gebruikt in klein- en grootschalige softwareontwikkeling.

Java-objecten in een relationele databasecontext worden gedefinieerd als entiteiten . Entiteiten worden in tabellen geplaatst waar ze kolommen en rijen bezetten. Programmeurs gebruiken externe sleutels en voegen zich bij tabellen om de relaties tussen entiteiten te definiëren, namelijk één-op-één, één-op-veel en veel-op-veel-relaties. We kunnen ook SQL (Structured Query Language) gebruiken om gegevens in afzonderlijke tabellen en in meerdere tabellen op te halen en ermee te werken, met behulp van externe sleutelbeperkingen. Het relationele model is plat, maar ontwikkelaars kunnen query's schrijven om gegevens op te halen en objecten uit die gegevens te construeren.

Object-relaties impedantie mismatch

U bent wellicht bekend met de term object-relaties impedantie-mismatch , die verwijst naar de uitdaging om data-objecten toe te wijzen aan een relationele database. Deze mismatch treedt op omdat objectgeoriënteerd ontwerp niet beperkt is tot een-op-een-, een-op-veel- en veel-op-veel-relaties. In plaats daarvan denken we bij objectgeoriënteerd ontwerpen aan objecten, hun attributen en gedrag, en hoe objecten zich verhouden. Twee voorbeelden zijn inkapseling en overerving:

  • Als een object een ander object bevat, definiëren we dit door inkapseling -a has-a- relatie.
  • Als een object een specialisatie is van een ander object, definiëren we dit door overerving - een is - relatie.

Associatie, aggregatie, compositie, abstractie, generalisatie, realisatie en afhankelijkheden zijn allemaal objectgeoriënteerde programmeerconcepten die moeilijk kunnen worden toegewezen aan een relationeel model.

ORM: Object-relationele mapping

De discrepantie tussen objectgeoriënteerd ontwerp en relationele databasemodellering heeft geleid tot een klasse tools die specifiek is ontwikkeld voor object-relationele mapping (ORM). ORM-tools zoals Hibernate, EclipseLink en iBatis vertalen relationele databasemodellen, inclusief entiteiten en hun relaties, in objectgeoriënteerde modellen. Veel van deze tools bestonden al vóór de JPA-specificatie, maar zonder een standaard waren hun functies afhankelijk van de leverancier.

De Java Persistence API (JPA), die in 2006 voor het eerst werd uitgebracht als onderdeel van EJB 3.0, biedt een standaardmanier om objecten te annoteren zodat ze kunnen worden toegewezen en opgeslagen in een relationele database. De specificatie definieert ook een algemeen construct voor interactie met databases. Het hebben van een ORM-standaard voor Java zorgt voor consistentie in implementaties van leveranciers, terwijl het ook zorgt voor flexibiliteit en add-ons. Terwijl de oorspronkelijke JPA-specificatie bijvoorbeeld van toepassing is op relationele databases, hebben sommige leveranciersimplementaties de JPA uitgebreid voor gebruik met NoSQL-databases.

Evolutie van de PPV

De eerste release van JPA, versie 1.0, werd gepubliceerd in 2006 via het Java Community Process (JCP) als Java Specification Request (JSR) 220. Versie 2.0 (JSR 317) werd gepubliceerd in 2009, versie 2.1 (JSR 338) in 2013, en versie 2.2 (een onderhoudsrelease van JSR 338) werd gepubliceerd in 2017. JPA 2.2 is geselecteerd voor opname en voortdurende ontwikkeling in Jakarta EE.

Aan de slag met JPA

De Java Persistence API is een specificatie, geen implementatie: het definieert een algemene abstractie die u in uw code kunt gebruiken om te communiceren met ORM-producten. In dit gedeelte worden enkele van de belangrijke onderdelen van de JPA-specificatie besproken.

U leert hoe u:

  • Definieer entiteiten, velden en primaire sleutels in de database.
  • Creëer relaties tussen entiteiten in de database.
  • Werk met de EntityManageren zijn methoden.

Entiteiten definiëren

Om een ​​entiteit te definiëren, moet u een klasse maken die wordt geannoteerd met de @Entityannotatie. De @Entityannotatie is een markeringsannotatie , die wordt gebruikt om blijvende entiteiten te ontdekken. Als u bijvoorbeeld een boekentiteit wilt maken, kunt u deze als volgt annoteren:

 @Entity public class Book { ... } 

Standaard wordt deze entiteit toegewezen aan de Booktabel, zoals bepaald door de opgegeven klassenaam. Als u deze entiteit aan een andere tabel (en optioneel een specifiek schema) wilt toewijzen, kunt u de @Tableannotatie daarvoor gebruiken . Hier ziet u hoe u de Bookklas toewijst aan een BOEKEN-tabel:

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

Als de BOOKS-tabel zich in het schema PUBLISHING bevond, zou je het schema aan de @Tableannotatie kunnen toevoegen :

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

Velden toewijzen aan kolommen

Nu de entiteit aan een tabel is toegewezen, is uw volgende taak het definiëren van de velden ervan. Velden worden gedefinieerd als lidvariabelen in de klasse, waarbij de naam van elk veld wordt toegewezen aan een kolomnaam in de tabel. U kunt deze standaardtoewijzing overschrijven door de @Columnannotatie te gebruiken, zoals hier wordt weergegeven:

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

In dit voorbeeld hebben we de standaardtoewijzing voor het namekenmerk geaccepteerd, maar een aangepaste toewijzing voor het isbnkenmerk gespecificeerd . Het nameattribuut zal worden toegewezen aan de naam van de kolom, maar de isbneigenschap zal worden toegewezen aan de ISBN_NUMBER kolom.

De @Columnannotatie stelt ons in staat om aanvullende eigenschappen van het veld / de kolom te definiëren, inclusief lengte, of het nullabel is, of het uniek moet zijn, de precisie en schaal (als het een decimale waarde is), of het kan worden ingevoegd en bijgewerkt, enzovoort. .

De primaire sleutel specificeren

Een van de vereisten voor een relationele databasetabel is dat deze een primaire sleutel moet bevatten , of een sleutel die op unieke wijze een specifieke rij in de database identificeert. In JPA gebruiken we de @Idannotatie om een ​​veld aan te wijzen als de primaire sleutel van de tabel. De primaire sleutel moet een Java-primitief type zijn, een primitieve wrapper, zoals Integeror Long, a String, a Date, a BigIntegerof a BigDecimal.

In dit voorbeeld idwijzen we het attribuut, dat een is Integer, toe aan de ID-kolom in de BOEKEN-tabel:

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

Het is ook mogelijk om de @Idannotatie te combineren met de @Columnannotatie om de kolomnaamtoewijzing van de primaire sleutel te overschrijven.

Relaties tussen entiteiten

Nu u weet hoe u een entiteit moet definiëren, gaan we kijken hoe u relaties tussen entiteiten kunt creëren. JPA definieert vier annotaties voor het definiëren van entiteiten:

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

Een-op-een-relaties

The @OneToOne annotation is used to define a one-to-one relationship between two entities. For example, you may have a User entity that contains a user's name, email, and password, but you may want to maintain additional information about a user (such as age, gender, and favorite color) in a separate UserProfile entity. The @OneToOne annotation facilitates breaking down your data and entities this way.

The User class below has a single UserProfile instance. The UserProfile maps to a single User instance.

 @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; ... } 

The JPA provider uses UserProfile's user field to map UserProfile to User. The mapping is specified in the mappedBy attribute in the @OneToOne annotation.

One-to-many and many-to-one relationships

The @OneToMany and @ManyToOne annotations facilitate both sides of the same relationship. Consider an example where a Book can have only one Author, but an Author may have many books. The Book entity would define a @ManyToOne relationship with Author and the Author entity would define a @OneToMany relationship with 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 this case, the Author class maintains a list of all of the books written by that author and the Book class maintains a reference to its single author. Additionally, the @JoinColumn specifies the name of the column in the Book table to store the ID of the Author.

Many-to-many relationships

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.

Maak je geen zorgen over het in één keer integreren van al deze methoden. U leert ze kennen door rechtstreeks met de te werken EntityManager, wat we in de volgende sectie meer zullen doen.