Java Persistence API
Java Persistence API (JPA), od roku 2019 nazývaný Jakarta Persistence[1], je standard programovacího jazyka Java, který umožňuje objektově relační mapování (ORM). To usnadňuje práci s ukládáním objektů do databáze a naopak. Je určen jak pro Java SE, tak pro Java EE/Jakarta EE.[2]
Entity
Entita je objekt, který reprezentuje data v databázi. Typicky entitní třída reprezentuje tabulku v relační databázi a každá instance této třídy pak koresponduje k jedné řádce tabulky.[3]
Požadavky pro entitní třídu
Aby mohly být entity persistovány, musí mít entitní třída následující vlastnosti[4]:
- Musí být anotována anotací javax.persistence.Entity
- Musí mít public nebo protected konstruktor bez parametrů. Může ale mít i další konstruktory.
- Nesmí být deklarována jako final. To platí i pro její metody.
- Pokud session beana bude pracovat s instancemi této třídy a bude typu Remote, potom tato entitní třída musí implementovat interface Serializable
- Může dědit z entitní i ne-entitní třídy.
- Její atributy musí být deklarovány jako private, protected nebo package-private a lze k nim přistupovat pouze přes metody (gettery a settery).
Povolené typy atributů
Aby mohla být entita uložena do databáze, musí mít atributy pouze následujících typů:[5]
- primitivní typy
- java.lang.String
- Wrapper třídy primitivních typů
- java.math.BigInteger
- java.math.BigDecimal
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
- java.sql.TimeStamp
- byte[]
- Byte[]
- char[]
- Character[]
- Enumerační typy
- Jiné entity a/nebo kolekce entit
- Třídy s anotací Embeddable
Životní cyklus entity
Každá entita má svůj určitý stav, ve kterém se nachází. Ten je rozhodnut instancí třídy EntityManger. Entita se pohybuje mezi jednotlivými stavy vždy po provedení určité akce EntityMangerem. Startovním stavem entity je vždy New/Transient, žádný stav není konečný. Všechny stavy entity a hrany mezi nimi vystihuje následující graf:[6]
Multiplicity
Existují 4 typy multiplicit v entitních třídách:
- One-to-one: instance má referenci na jednu entitu jiné třídy.
- One-to-many: instance má reference na množinu instancí jiné třídy.
- Many-to-one: více instancí má referenci na jednu instanci jiné třídy.
- Many-to-many: více instancí má reference na instance jiné třídy.
Dále existují 2 typy direction:
- Unidirectional: instance má referenci na jiný objekt, ten ovšem nemá referenci zpět.
- Bidirectional: instance má referenci na jiný objekt a ten má také referenci zpět. Vidí se navzájem.
Persistence Context
Entity jsou spravovány objektem třídy EntityManager. Chceme-li ho získat v SessionBeaně, musíme deklarovat atribut typu EntityManager a anotovat ho pomocí PersistenceContext.
Základní metody EntityManageru pro práci s objekty
Předpokládejme, že máme definován EntityManager s názvem em a entitu s názvem entita:
- em.persist(entita): uloží objekt entita do databáze (operace INSERT)
- em.remove(entita): smaže objekt entita z databáze (operace DELETE)
- em.merge(entita): entita byla persistována, ale následně byla změněna. Po operaci merge se tyto změny projeví v databázi (operace UPDATE).
- em.find(class,id): vrátí objekt v tabulce, která koresponduje s class a má primární klíč id (operace SELECT)
Transakce
Veškeré operace spojené s přístupem do databáze (operace INSERT, DELETE, UPDATE apod.) jsou prováděny uvnitř metod v Enterprise Java Beanách pomocí instance třídy EntityManager. Celá tato metoda je pak brána jako jedna ACID transakce. To mimo jiné znamená, že pokud dojde k provedení celé metody bez vyhození výjimky, bude zavoláno commit a celá transakce se potvrdí. Naopak, pokud dojde k vyhození výjimky, je zavoláno rollback a co se doposud provedlo bude vráceno a databáze tak zůstane v původním konzistentím stavu.
Nicméně je také možné se starat o transakce ručně:
@PersistenceContext
private EntityManager em;
public void foo(){
EntityTransaction ut=null;
try{
ut=em.getTransaction();
ut.begin();
//do work
ut.commit();
} catch(Exception ex){
ut.rollback();
}
}
Java Persistence Query Language
EntityManager umí vytvářet dotazy podobné SQL dotazům. Využívá se zde ovšem Java Persistence Query Language[7], což je jazyk podobný SQL a jeho použití má tu výhodu, že dotazy jsou nezávislé na zvolené technologii databáze (Oracle, Microsoft…). Navíc mají objektové vlastnosti, takže v dotazech nezmiňujeme konkrétní názvy tabulek databáze či jejich vlastností, nýbrž v dotazech uvádíme přímo názvy tříd/atributů, k porovnávání můžeme využívat i referencí objektů.
Implementace JPA
Java Persistence API je pouze specifikací, nicméně není to samotná implementace, která by se dala použít. Mezi implementace JPA patří tyto produkty:
- EclipseLink
- Hibernate
- Oracle Toplink
- OpenJPA
Příklad
Máme majitele (Owner), který může mít několik aut (Car). Dále předpokládejme, že každé auto může mít vždy pouze jednoho majitele a že u auta je majitel uveden (použijeme biderectional relationship - oboustranný vztah).
Třída Owner
@Entity
public class Owner implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "owner")
private List<Car> car;
public List<Car> getCar() {
return car;
}
public void setCar(List<Car> car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Owner)) {
return false;
}
Owner other = (Owner) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "Owner[id=" + id + "]";
}
}
Třída Car
@Entity
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String color;
@ManyToOne
private Owner owner;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Owner getOwner() {
return owner;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Car)) {
return false;
}
Car other = (Car) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "Car[id=" + id + "]";
}
}
Session Bean
@Stateless
public class ExampleEjbBean implements ExampleEjbLocal {
@PersistenceContext
EntityManager em;
public void saveOwner(Owner owner) {
em.persist(owner);
}
public Owner getOwner(Long id) {
return (Owner) em.find(Owner.class, id);
}
}
Reference
- GUINDON, Christopher. Jakarta Persistence [online]. Jakarta EE [cit. 2019-09-14]. Dostupné online. (anglicky)
- Sun's Java Persistence API
- "Entities - The Java EE 5 Tutorial".
- "Requirements for Entity Classes - The Java EE 5 Tutorial".
- "Persistent Fields and Properties in Entity Classes".
- "Apache OpenJPA User's Guide - Entity Lifecycle Management".
- "The Java Persistence Query Language - The Java 5 EE Tutorial".