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 EntityManager
med 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
EntityManager
och dess metoder.
Definiera enheter
För att definiera en enhet måste du skapa en klass som antecknas med @Entity
anteckningen. Den @Entity
anteckning ä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 Book
tabellen, 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 @Table
kommentaren för att göra det. Så här kartlägger du Book
klassen 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 @Table
kommentaren:
@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 @Column
anteckningen, 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 name
attributet men specificerat en anpassad mappning för isbn
attributet. Den name
attribut kommer att mappas till namn kolumnen men isbn
attribut kommer att mappas till ISBN_NUMBER kolumnen.
Den @Column
annotering 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 @Id
anteckningen 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 Integer
eller Long
, a String
, a Date
, a BigInteger
eller 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 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.
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.