Java-uthållighet med JPA och Hibernate, del 1: enheter och relationer

Java Persistence API (JPA) är en Java-specifikation som överbryggar klyftan mellan relationsdatabaser och objektorienterad programmering. Denna tvådelade handledning introducerar JPA och förklarar hur Java-objekt modelleras som JPA-enheter, hur enhetsförhållanden definieras och hur man använder JPA: er EntityManagermed förvarets mönster i dina Java-applikationer.

Observera att denna handledning använder viloläge som JPA-leverantör. De flesta begrepp kan utökas till andra Java-uthållighetsramar.

Vad är JPA?

Se "Vad är JPA? Introduktion till Java Persistence API" för att lära dig om utvecklingen av JPA och relaterade ramar, inklusive EJB 3.0. och JDBC.

Objektrelationer i JPA

Relationsdatabaser har funnits som ett sätt att lagra programdata sedan 1970-talet. Medan utvecklare idag har många alternativ till relationsdatabasen, är denna typ av databas skalbar och väl förstådd och används fortfarande i stor utsträckning i små och stora programvaruutvecklingar.

Java-objekt i en relationsdatabaskontext definieras som enheter . Enheter placeras i tabeller där de upptar kolumner och rader. Programmerare använder främmande nycklar och går med i tabeller för att definiera relationerna mellan enheter - nämligen en-till-en, en-till-många och många-till-många-relationer. Vi kan också använda SQL (Structured Query Language) för att hämta och interagera med data i enskilda tabeller och över flera tabeller med hjälp av främmande nyckelbegränsningar. Relationsmodellen är platt, men utvecklare kan skriva frågor för att hämta data och konstruera objekt från den informationen.

Objekt-relationer impedans obalans

Du kanske är bekant med termen object-relations impedans mismatch , som hänvisar till utmaningen att mappa dataobjekt till en relationsdatabas. Denna oöverensstämmelse uppstår eftersom objektorienterad design inte är begränsad till en-till-en, en-till-många och många-till-många relationer. I stället, i objektorienterad design, tänker vi på objekt, deras attribut och beteende, och hur objekt relaterar. Två exempel är inkapsling och arv:

  • Om ett föremål innehåller ett annat ändamål, definierar vi detta genom inkapsling --a har-en relation.
  • Om ett objekt är en specialisering av ett annat objekt definierar vi detta genom arv - ett är-en- relation.

Associering, aggregering, komposition, abstraktion, generalisering, förverkligande och beroenden är alla objektorienterade programmeringskoncept som kan vara utmanande att kartlägga till en relationsmodell.

ORM: Objektrelationell kartläggning

Ojämnheten mellan objektorienterad design och relationsdatabasmodellering har lett till en klass av verktyg som utvecklats specifikt för objektrelationskartläggning (ORM). ORM-verktyg som Hibernate, EclipseLink och iBatis översätter relationsdatabasmodeller, inklusive enheter och deras relationer, till objektorienterade modeller. Många av dessa verktyg fanns före JPA-specifikationen, men utan standard var deras funktioner leverantörsberoende.

Java Persistence API (JPA) släpptes först som en del av EJB 3.0 2006 och erbjuder ett vanligt sätt att kommentera objekt så att de kan kartläggas och lagras i en relationsdatabas. Specifikationen definierar också en gemensam konstruktion för interaktion med databaser. Att ha en ORM-standard för Java ger konsistens i leverantörsimplementeringar, samtidigt som det möjliggör flexibilitet och tillägg. Som ett exempel, medan den ursprungliga JPA-specifikationen är tillämplig på relationsdatabaser, har vissa leverantörsimplementeringar utökat JPA för användning med NoSQL-databaser.

Utvecklingen av JPA

Den första versionen av JPA, version 1.0, publicerades 2006 genom Java Community Process (JCP) som Java Specification Request (JSR) 220. Version 2.0 (JSR 317) publicerades 2009, version 2.1 (JSR 338) 2013, och version 2.2 (en underhållsutgåva av JSR 338) publicerades 2017. JPA 2.2 har valts för inkludering och pågående utveckling i Jakarta EE.

Komma igång med JPA

Java Persistence API är en specifikation, inte en implementering: den definierar en vanlig abstraktion som du kan använda i din kod för att interagera med ORM-produkter. Detta avsnitt granskar några av de viktiga delarna av JPA-specifikationen.

Du lär dig hur du:

  • Definiera enheter, fält och primära nycklar i databasen.
  • Skapa relationer mellan enheter i databasen.
  • Arbeta med EntityManageroch dess metoder.

Definiera enheter

För att definiera en enhet måste du skapa en klass som antecknas med @Entityanteckningen. Den @Entityanteckning är en markör anteckning , som används för att upptäcka persistenta enheter. Om du till exempel vill skapa en bokenhet, skulle du kommentera den på följande sätt:

 @Entity public class Book { ... } 

Som standard mappas denna enhet till Booktabellen, som bestäms av det angivna klassnamnet. Om du vill mappa denna enhet till en annan tabell (och eventuellt ett specifikt schema) kan du använda @Tablekommentaren för att göra det. Så här kartlägger du Bookklassen till en BOOKS-tabell:

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

Om BOOKS-tabellen var i schemat PUBLICERING kan du lägga till schemat i @Tablekommentaren:

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

Kartlägga fält till kolumner

Med enheten mappad till en tabell är din nästa uppgift att definiera dess fält. Fält definieras som medlemsvariabler i klassen, där namnet på varje fält mappas till ett kolumnnamn i tabellen. Du kan åsidosätta denna standardmappning genom att använda @Columnanteckningen, som visas här:

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

I det här exemplet har vi accepterat standardmappningen för nameattributet men specificerat en anpassad mappning för isbnattributet. Den nameattribut kommer att mappas till namn kolumnen men isbnattribut kommer att mappas till ISBN_NUMBER kolumnen.

Den @Columnannotering ger oss möjlighet att definiera ytterligare egenskaper av fältet / kolumn, inklusive längd, om det är null, om det måste vara unik, dess precision och omfattning (om det är ett decimalvärde), om det är införbart och uppdateras, och så vidare .

Ange primärnyckel

Ett av kraven för en relationsdatabastabell är att den måste innehålla en primär nyckel eller en nyckel som unikt identifierar en specifik rad i databasen. I JPA använder vi @Idanteckningen för att beteckna ett fält som bordets primära nyckel. Primärnyckeln krävs för att vara en primitiv Java-typ, ett primitivt omslag, såsom Integereller Long, a String, a Date, a BigIntegereller a BigDecimal.

In this example, we map the id attribute, which is an Integer, to the ID column in the BOOKS table:

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

It is also possible to combine the @Id annotation with the @Column annotation to overwrite the primary key's column-name mapping.

Relationships between entities

Now that you know how to define an entity, let's look at how to create relationships between entities. JPA defines four annotations for defining entities:

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

One-to-one relationships

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.

Oroa dig inte för att integrera alla dessa metoder samtidigt. Du lär känna dem genom att arbeta direkt med EntityManager, vilket vi gör mer i nästa avsnitt.