Domů » Informatika » Programovací jazyk » Jazyk Java » JPA (Java Persistence API)


JPA (Java Persistence API)

JPA (Java Persistence API) je standard popisující programátorské rozhraní (API) a chování knihoven pro objektově-relační mapování. V současné době existují tyto jeho nejznámnější implementace:

Entita

Entitou může být každá třída, splňující předepsané podmínky:

  • anotace @Entity (javax.persis­tence.Entity)
  • alespoň jeden veřejný či chráněný konstruktor bez parametrů
  • třída ani její metody nesmí být final
  • její atributy nejsou veřejné a ke každému existuje settergetter

Tato třída se zpravidla ukládá jako jeden řádek v nějaké databázové tabulce (pokud je jako úložiště použita relační databáze).

Entita s minimem anotací

kód v jazyce Java - Zobrazit

  1. @Entity
  2. public class Person {
  3.     @Id
  4.     @GeneratedValue
  5.     private Long id;
  6.     private String name;
  7.     private String surname;
  8.     private int age;
  9.  
  10.     // ...
  11.     // gettery
  12.     // settery
  13.     // ...
  14. }
Entita s podrobnými anotacemi

kód v jazyce Java - Zobrazit

  1. @Entity(name = "person")
  2. @Table(name = "person")
  3. public class Person {
  4.     @Id
  5.     @GeneratedValue
  6.     private Long id;
  7.     @Column(name = "name", length = 30, nullable = false)
  8.     private String name;
  9.     @Column(name = "surname", length = 50, nullable = false)
  10.     private String surname;
  11.     @Column(name = "age", precision = 3, scale = 0, nullable = false)
  12.     private int age;
  13.  
  14.     // ...
  15.     // gettery
  16.     // settery
  17.     // ...
  18. }
Entita s definicí ekvivalence

kód v jazyce Java - Zobrazit

  1. @Entity
  2. public class Person {
  3.     @Id
  4.     @GeneratedValue
  5.     private Long id;
  6.  
  7.     @Override
  8.     public int hashCode()
  9.     {
  10.         return (id == null) ? 0 : id.hashCode();
  11.     }
  12.  
  13.     @Override
  14.     public boolean equals(Object other)
  15.     {
  16.       if (other == this)
  17.       {
  18.         // dvě stejné instance jsou si vždy rovny
  19.         return true;
  20.       }
  21.  
  22.       if (!(other instanceof Person))
  23.       {
  24.         // druhá instance není správného typu nebo je NULL
  25.         // (operátor 'instanceof' povolí i podtřídy)
  26.         return false;
  27.       }
  28.  
  29.       // pro další zjednodušení se druhá instance přetypuje
  30.       Person otherPerson = (Person) other;
  31.  
  32.       if (this.id == null || otherPerson.id == null)
  33.       {
  34.         // bez obou klíčů nelze rozhodnout o rovnosti instancí
  35.         return false;
  36.       }
  37.  
  38.       if (this.id != otherPerson.id)
  39.       {
  40.         // primární klíče obou instancí se liší
  41.         return false;
  42.       }
  43.  
  44.       // obě instance jsou osoby se stejným klíčem
  45.       return true;
  46.     }
  47.  
  48.     // ...
  49.     // gettery
  50.     // settery
  51.     // ...
  52. }

Persistenční kontext

Jako persistenční kontext (persistence context) se označuje množina instancí entit, které jsou spravovány jedním správcem entit. Životní cyklus persitenčního kontextu je zpravidla svázán s transakcí (srovnej Unit of Work). Pochopení smyslu persistenčního kontextu je důležité pro správný vývoj aplikací a korektní řízení transakcí.

V jednom persistenčním kontextu vždy existuje nejvýše jedna instance entity daného typu se stejným primárním klíčem (srovnej Identity Map). Proto na něj lze pohlížet i jako na určitou formu vyrovnávací paměti (cache), ve které jsou jednotlivé instance entit adresovány svými typy a primárními klíči.

Stav entit v persistenčním kontextu nemusí odpovídat stavu v úložišti. Aby došlo k trvalému uložení všech lokálně provedených změn, musí dojít k tzv. synchronizaci, která je vyvolána na konci transakce (při zavolání metody commit() na transakci) nebo ručně zavoláním metody flush() na správci entit. Během synchronizace může dojít k chybám, které jsou způsobeny například porušením integritních omezení nebo neschopností získat potřebný zámek.

V případě, že potvrzování transakce selže, je sice automaticky provedeno zrušení transakce v databázi (rollback), ale entity se do původního stavu před transakcí nevrací. Je proto nutné je znovu načíst z databáze a provést celou transakci znovu.

Instance entit v persistenčním kontextu se označují jako spravované (managed), instance mimo persistenční kontext se označují jako odpojené (detached). Odpojené entity nemá správce entit pod kontrolou a neprovádí s nimi žádné operace. Uchovávají si ale svou identitu a je proto jednoduché je znovu načíst (například dle primárního klíče).

Správa entit

O správu entit a jejich životní cyklus (tedy o načítání, ukládání, mazání a obnovování) se stará třída implementující rozhraní EntityManager. Toto rozhraní předepisuje následující klíčové metody:

  • find – načte entitu se zadaným klíčem z úložiště do kontextu
  • refresh – obnoví entitu v kontextu dle úložiště
  • persist – přidá entitu do kontextu
  • merge – upraví entitu v úložišti dle kontextu
  • remove – odebere entitu z úložiště
  • detach – odebere entitu z kontextu

Libovolná implementace tohoto rozhraní bude v textu dále označována jako správce entit.

Životní cyklus entity

Po vytvoření nové instance správce entit je persistenční kontext prázdný. Pomocí dotazů (viz dále) a metodou find je možné do kontextu načíst aktuální entity z databáze, metodou persist je do kontextu vložena nová (lokálně vytvořená) instance entity. Poté je možné entity libovolně modifikovat pomocí setterů. Metodou refresh je možné aktualizovat instanci entity v kontextu a zrušit tak její případné neuložené změny. Metoda remove označí entitu v kontextu jako určenou ke smazání, metoda detach ji odstraní z persistenčního kontextu (nikoliv z databáze).

životní cyklus entity

Transakce

kód v jazyce Java - Zobrazit

  1. // vytvořit několik instancí entit
  2.  
  3. Company company = new Company();
  4. company.setName("SuperTech a.s.");
  5.  
  6. Employee employee = new Employee();
  7. employee.setName("Jan");
  8. employee.setSurname("Novák");
  9.  
  10. // vytvořit relaci (nutno svázat obě strany)
  11.  
  12. company.addEmployee(employee);
  13.  
  14. // TRANSAKCE
  15. // =========
  16.  
  17. EntityManager em = emf.createEntityManager();
  18.  
  19. em.getTransaction().begin();
  20.  
  21. // kontext je prázdný
  22.  
  23. em.persist(company);
  24.  
  25. // kontext: {company}
  26.  
  27. em.persist(employee);
  28.  
  29. // kontext: {company, employee}
  30.  
  31. em.getTransaction().commit();
  32.  
  33. // kontext byl vyprázdněn,
  34. // instance "company" a "employee" jsou odpojené (detached)
Doporučení

Na každou transakci se doporučuje vytvořit nového správce entit, aby se v něm při chybách nehromadily nepotřebné entity a nezpůsobovaly tak zbytečné memory leaky. Ihned po vytvoření správce entit je nanejvýš vhodné zahájit transakcí a po jejím potvrzení či zrušení správce entit uzavřít příkazem close(). Po uzavření již není možné správce entit používat (dojde k výjimce) a tak je při ladění programu nalezeno místo, kde se se správcem pracuje mimo povolenou transakci.

Vazby

1:1 (one-to-one)
1:N (one-to-many)

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Krabice, která může obsahovat předměty.
  3.  * @author Vojtěch Hordějčuk
  4.  */
  5. @Entity(name = "box")
  6. @Table(name = "box")
  7. public class Box implements Serializable
  8. {
  9.   private static final long serialVersionUID = 1L;
  10.   /**
  11.    * ID krabice
  12.    */
  13.   @Id
  14.   @GeneratedValue(strategy = GenerationType.SEQUENCE)
  15.   @Column(name = "box_id", nullable = false)
  16.   private Long id;
  17.   /**
  18.    * obsah krabice = položky
  19.    */
  20.   @OneToMany(mappedBy = "location")
  21.   @JoinColumn(name = "contents", nullable = false)
  22.   private List<Item> contents;
  23.  
  24.   // ...
  25. }
N:1 (many-to-one)

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Předmět, který může být umístěn v krabici.
  3.  * @author Vojtěch Hordějčuk
  4.  */
  5. @Entity(name = "item")
  6. @Table(name = "item")
  7. public class Item implements Serializable
  8. {
  9.   private static final long serialVersionUID = 1L;
  10.   /**
  11.    * ID předmětu
  12.    */
  13.   @Id
  14.   @GeneratedValue(strategy = GenerationType.SEQUENCE)
  15.   @Column(name = "item_id", nullable = false)
  16.   private Long id;
  17.   /**
  18.    * krabice, ve které se předmět nachází
  19.    */
  20.   @ManyToOne(optional = true)
  21.   @JoinColumn(name = "box_ref", nullable = true)
  22.   private Box location;
  23.  
  24.   // ...
  25. }
N:N (many-to-many)

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Projekt. Na projektu může pracovat více zaměstnanců.
  3.  * @author Vojtěch Hordějčuk
  4.  */
  5. @Entity(name = "project")
  6. @Table(name = "project")
  7. public class Project implements Serializable
  8. {
  9.   private static final long serialVersionUID = 1L;
  10.   /**
  11.    * ID projektu
  12.    */
  13.   @Id
  14.   @GeneratedValue(strategy = GenerationType.AUTO)
  15.   @Column(name = "project_id", nullable = false)
  16.   private Long id;
  17.   /**
  18.    * přiřazení zaměstnanci
  19.    */
  20.   @ManyToMany(mappedBy = "projects")
  21.   private List<Employee> employees;
  22. }

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Zaměstnanec. Může pracovat na několika projektech.
  3.  * @author Vojtěch Hordějčuk
  4.  */
  5. @Entity(name = "employee")
  6. @Table(name = "employee")
  7. public class Employee implements Serializable
  8. {
  9.   private static final long serialVersionUID = 1L;
  10.   /**
  11.    * jméno zaměstnance
  12.    */
  13.   @Id
  14.   @Column(name = "employee_name", nullable = false)
  15.   private String name;
  16.   /**
  17.    * seznam projektů
  18.    */
  19.   @ManyToMany
  20.   @JoinTable(name = "assignment",
  21.   joinColumns =
  22.   {
  23.     @JoinColumn(name = "employee_ref", referencedColumnName = "employee_name", nullable = false)
  24.   },
  25.   inverseJoinColumns =
  26.   {
  27.     @JoinColumn(name = "project_id", referencedColumnName = "project_id", nullable = false)
  28.   })
  29.   private List<Project> projects;
  30. }

Konfigurace persistenční jednotky

Persistenční jednotka obsahuje konfiguraci připojení k databázi, jeho parametry a konkrétní knihovnu, která bude k tomuto účelu bude využita jako implementace JPA. Jednotka se konfiguruje ve speciálním XML souboru, který je umístěn v adresáři …/src/META-INF/persisten­ce.xml. Následuje ukázkový konfigurační soubor pro databázi MySQL a knihovnu EclipseLink:

kód v jazyce XML - Zobrazit

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  3.   <persistence-unit name="voho" transaction-type="RESOURCE_LOCAL">
  4.     <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  5.     <shared-cache-mode>NONE</shared-cache-mode>
  6.     <properties>
  7.       <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/DBNAME"/>
  8.       <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
  9.       <property name="javax.persistence.jdbc.user" value="USER"/>
  10.       <property name="javax.persistence.jdbc.password" value="PASSWORD"/>
  11.       <property name="eclipselink.ddl-generation" value="create-tables"/>
  12.     </properties>
  13.   </persistence-unit>
  14. </persistence>

Získání správce entit

Správce entit lze získat různými způsoby. Aplikace postavené na technologii Java EE umožňují například tzv. dependency injection. Pokud tento postup není dostupný či vítaný, je tu ještě druhá možnost:

kód v jazyce Java - Zobrazit

  1. String pu = "název persistenční jednotky";
  2. EntityManagerFactory emf = Persistence.createEntityManagerFactory(pu);
  3. EntityManager em = emf.createEntityManager();

Persistenční jednotka je definovaná v souboru persistence.xml.

Reference