Postojanost Java s JPA i Hibernate, 1. dio: Entiteti i odnosi

API trajnosti Java (JPA) Java je specifikacija koja premošćuje jaz između relacijskih baza podataka i objektno orijentiranog programiranja. Ovaj dvodijelni vodič predstavlja JPA i objašnjava kako su Java objekti modelirani kao JPA entiteti, kako su definirani odnosi entiteta i kako koristiti JPA EntityManagers uzorkom Repozitorija u vašim Java aplikacijama.

Imajte na umu da ovaj vodič koristi Hibernate kao JPA pružatelja usluga. Većina koncepata može se proširiti na druge okvire trajnosti Java.

Što je JPA?

Pogledajte "Što je JPA? Uvod u Java Persistent API" da biste saznali više o razvoju JPA i srodnih okvira, uključujući EJB 3.0. i JDBC.

Predmetni odnosi u JPA

Relacijske baze podataka postoje kao sredstvo za pohranu programskih podataka od 1970-ih. Iako programeri danas imaju mnogo alternativa relacijskoj bazi podataka, ova vrsta baze podataka je skalabilna i dobro razumljiva te se i dalje široko koristi u razvoju malih i velikih softvera.

Java objekti u kontekstu relacijske baze podataka definirani su kao entiteti . Entiteti se postavljaju u tablice gdje zauzimaju stupce i retke. Programeri koriste strane ključeve i spajaju tablice kako bi definirali odnose između entiteta - naime jedan-na-jedan, jedan-na-mnogo i odnosi mnogo-na-mnogo. Također možemo koristiti SQL (jezik strukturiranih upita) za dohvaćanje podataka i interakciju s njima u pojedinačnim tablicama i više njih, koristeći ograničenja stranog ključa. Relacijski model je ravan, ali programeri mogu pisati upite za dohvaćanje podataka i konstruiranje objekata iz tih podataka.

Neusklađenost impedancije objektnih odnosa

Možda vam je poznat pojam neusklađenosti impedancije relacija objekt , koji se odnosi na izazov mapiranja podatkovnih objekata u relacijsku bazu podataka. Do ove neusklađenosti dolazi zato što objektno orijentirani dizajn nije ograničen na odnose jedan-na-jedan, jedan-na-mnogo i mnogo-na-mnogo. Umjesto toga, u objektno orijentiranom dizajnu razmišljamo o objektima, njihovim atributima i ponašanju te o tome kako se objekti povezuju. Dva su primjera inkapsulacija i nasljeđivanje:

  • Ako objekt sadrži drugi objekt, to definiramo enkapsulacijom - odnos ima-ima .
  • Ako je objekt specijalizacija drugog objekta, to definiramo nasljeđivanjem - to je- odnos.

Asocijacija, agregacija, sastav, apstrakcija, generalizacija, realizacija i ovisnosti sve su to objektno orijentirani koncepti programiranja koje može biti izazov mapirati u relacijski model.

ORM: Objektno-relacijsko mapiranje

Neusklađenost između objektno orijentiranog dizajna i relacijskog modeliranja baze podataka dovela je do klase alata razvijenih posebno za objektno-relacijsko mapiranje (ORM). ORM alati poput Hibernate, EclipseLink i iBatis prevode modele relacijskih baza podataka, uključujući entitete i njihove odnose, u objektno orijentirane modele. Mnogi od ovih alata postojali su prije JPA specifikacije, ali bez standarda njihove su značajke ovisile o dobavljaču.

Prvi put objavljen kao dio EJB 3.0 2006. godine, Java Persistent API (JPA) nudi standardni način označavanja objekata tako da se mogu mapirati i pohraniti u relacijsku bazu podataka. Specifikacija također definira zajednički konstrukt za interakciju s bazama podataka. Imati ORM standard za Javu donosi dosljednost implementacijama dobavljača, a istovremeno omogućava fleksibilnost i dodatke. Kao primjer, dok je izvorna JPA specifikacija primjenjiva na relacijske baze podataka, neke implementacije dobavljača proširile su JPA za upotrebu s NoSQL bazama podataka.

Evolucija JPA

Prvo izdanje JPA, verzija 1.0, objavljeno je 2006. putem Java Community Process (JCP) kao Zahtjev za specifikaciju Java (JSR) 220. Verzija 2.0 (JSR 317) objavljena je 2009., verzija 2.1 (JSR 338) 2013. godine, a verzija 2.2 (izdanje za održavanje JSR 338) objavljena je 2017. JPA 2.2 odabrana je za uključivanje i stalni razvoj u Jakarta EE.

Početak rada sa JPA

API za trajnost Java specifikacija je, a ne implementacija: definira uobičajenu apstrakciju koju možete koristiti u svom kodu za interakciju s ORM proizvodima. Ovaj odjeljak daje pregled nekih važnih dijelova JPA specifikacije.

Naučit ćete kako:

  • U bazi podataka definirajte entitete, polja i primarne ključeve.
  • Stvorite odnose između entiteta u bazi podataka.
  • Radite s EntityManageri njegovim metodama.

Definiranje entiteta

Da biste definirali entitet, morate stvoriti klasu koja je označena @Entitybilješkom. @EntityPrimjedba je marker komentarima , koji se koristi za otkrivanje trajne entitete. Na primjer, ako želite stvoriti entitet knjige, dodali biste ga na sljedeći način:

 @Entity public class Book { ... } 

Prema zadanim postavkama, ovaj će se entitet preslikati u Booktablicu, kako je određeno danim nazivom klase. Ako ste ovaj entitet željeli preslikati u drugu tablicu (i, po želji, u određenu shemu), za to biste mogli upotrijebiti @Tablebilješku. Evo kako biste mapirali Bookrazred u tablicu BOOKS:

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

Ako je tablica KNJIGE bila u shemi PUBLISHING, shemu možete dodati u @Tablebilješku:

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

Mapiranje polja u stupce

S entitetom mapiranim u tablicu, sljedeći je zadatak definirati njegova polja. Polja su definirana kao varijable člana u klasi, s tim da se ime svakog polja preslikava u naziv stupca u tablici. Možete nadjačati ovo zadano mapiranje pomoću @Columnbilješke, kao što je prikazano ovdje:

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

U ovom smo primjeru prihvatili zadano mapiranje za nameatribut, ali naveli smo prilagođeno mapiranje za isbnatribut. nameAtribut će se mapirati na ime stupca, ali isbnatribut će se mapirati na ISBN_NUMBER kolonu.

@ColumnKomentarima nam omogućava definiranje dodatnih svojstava polju / stupcu, uključujući i dužine, da li je nulte, da li to mora biti jedinstven, njegova preciznost i skale (ako je decimalna vrijednost), da li se može umetnuti i updatable, i tako dalje .

Određivanje primarnog ključa

Jedan od zahtjeva za relacijskom tablicom baze podataka jest da mora sadržavati primarni ključ ili ključ koji jedinstveno identificira određeni redak u bazi podataka. U JPA, napomenom koristimo @Idpolje za primarni ključ tablice. Primarni ključ mora biti Java primitivni tip, primitivni omot, kao što je Integerili Long, a String, a Date, a BigIntegerili 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.

Ne brinite se hoćete li integrirati sve ove metode odjednom. Upoznat ćete ih radeći izravno s timom EntityManager, što ćemo učiniti više u sljedećem odjeljku.