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]


EntityEditovat

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říduEditovat

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ůEditovat

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 entityEditovat

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] 

MultiplicityEditovat

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 ContextEditovat

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 objektyEditovat

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)

TransakceEditovat

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 LanguageEditovat

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 JPAEditovat

Java Persistence API je pouze specifikací, nicméně není to samotná implementace, která by se dala použít. Mezi implementace JPA patří tyto produkty:

PříkladEditovat

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 OwnerEditovat

@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 CarEditovat

@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 BeanEditovat

@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);
    }
}

ReferenceEditovat

Související článkyEditovat

Externí odkazyEditovat