Java Persistence API
Java Persistence API (JPA) je framework 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.[1]
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.[2]
Požadavky pro entitní třídu
Aby mohly být entity persistovány, musí mít entitní třída následující vlastnosti[3]:
- 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ů:[4]
- 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
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žinou 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.
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 mimojiné 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();
}
}
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)
Java Persistence Query Language
EntityManager umí vytvářet dotazy podobné SQL dotazům. Využívá se zde ovšem Java Persistence Query Language[5], 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:
- Oracle Toplink
- Hibernate
- 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);
}
}