create table author (id int auto_increment primary key, name varchar(30), lastname varchar(30),
nationality varchar(3), born_year int(4));
create table book (id int auto_increment primary key, title varchar(100), author int, year int(4),
genre varchar(30), borrowed tinyint(1));
create table locality (id int auto_increment primary key, cabinet varchar(30), shelf varchar(30), id_book int);
create table borrowing (id int auto_increment primary key, to_whom varchar(50),
since date, until date, state tinyint(1));
create table borrowed_items (id_borrowing int, id_book int);
insert into author set name="Josef", lastname="Lada", nationality="CZE", born_year=1887;
insert into book set title="Do světa", author=1, year=1935, genre="fairy tale", borrowed=0;
insert into book set title="O Mikešovi", author=1, year=1934, genre="fairy tale", borrowed=0;
insert into author set name="Ondřej", lastname="Sekora", nationality="CZE", born_year=1899;
insert into book set title="Ferda Mravenec", author=2, year=1936, genre="fairy tale", borrowed=0;
insert into book set title="Trampoty brouka Pytlíka", author=2, year=1939, genre="fairy tale", borrowed=0;
Reláciu many-to-many reprezentujeme pomocou samostatnej tabuľky borrowed-items. Ostatné tabuľky obsahujú entity. Po vytvorení projektu je potrebné vytvoriť DataSource a ConnectionPool, čo sa robí konfiguráciou v súbore src/main/webapp/WEB-INF/glassfish-properties.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-connection-pool name="java:app/jdbc/eeDataSource"
res-type="javax.sql.DataSource"
datasource-classname="com.mysql.cj.jdbc.MysqlDataSource"
pool-resize-quantity="2"
max-pool-size="32"
steady-pool-size="8">
<property name="URL" value="jdbc:mysql://meno.servra.com:3306/meno_databazy"/>
<property name="User" value="pouzivatel"/>
<property name="Password" value="heslo"/>
<property name="autoReconnect" value="true" />
<property name="useSSL" value="false" />
</jdbc-connection-pool>
<jdbc-resource enabled="true" jndi-name="java:app/jdbc/eeDBresource" pool-name="java:app/jdbc/eeDataSource">
<description>DataSource jdbc/eeDataSource</description>
</jdbc-resource>
</resources>
Okrem toho potrebujeme nastaviť persistence unit, cez ktorý sa bude diať celá komunikácia s databázou v kóde. Ten sa obracia na príslušný DataSource, ktorý sme definovali vyššie.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="ee">
<jta-data-source>java:app/jdbc/eeDBresource</jta-data-source>
<class>ee.books.db.Author</class>
<class>ee.books.db.Book</class>
<class>ee.books.db.Borrowing</class>
<class>ee.books.db.Locality</class>
</persistence-unit>
</persistence>
Vymenovanie tried je potrebné preto, aby boli automaticky nájdené všetky JPA anotácie, ktoré sa v nich nachádzajú, keďže entity sú umiestnené v samostatnom pod-package db.
Aplikácia bude pozostávať z nasledujúcich častí:
- jednotlivé JSF stránky (súbory .xhtml)
- request-scope CDI bean, ktorý bude so stránkami vymieňať zobrazované a zadávané údaje (niečo ako controller)
- EJB stateless bean, ktorý bude tvoriť rozhranie (facade) medzi prezentačnou vrstvou a databázou a bude obsahovať všetok kód, ktorý bude vymieňať údaje s databázou
- triedy, ktoré definujú entity - zodpovedajú jednotlivým riadkom (záznamom) v tabuľkách relačnej databázy a ktoré využíva celá aplikácia - od JSF stránok, cez CDI bean až po EJB bean.
Vytvoríme nasledujúce JSF stránky:
- index.xhtml - hlavná stránka, ktorá zobrazí počet knižiek v knižnici a odkazy na ostatné stránky
- authors.xhtml - zobrazí zoznam evidovaných autorov, umožní ich vymazať alebo pridať nových
- books.xhtml - zobrazí zoznam evidovaných kníh, umožní ich, vymazať, pridať, vypožičať, alebo im nastaviť lokalitu
- borrowings.xhtml - zobrazí históriu všetkých evidovaných výpožičiek
- borrow.xhtml - umožní vytvoriť novú výpožičku (jednej alebo viacerých kníh)
- return.xhtml - umožní vrátiť (uzavrieť) predtým vytvorenú výpožičku
- setlocality.xhtml - umožní zadať umiestnenie konkrétnej jednej knižky
Entity budú dekorované anotáciami @Entity a @Table. Okrem toho môžeme vytvoriť pomenované dopyty pomocou anotácie @NamedQuery. Do Intellij odporúčame doinštalovať plugin JPA Buddy, pomocou ktorého entitné triedy môžeme vygenerovať automaticky, napríklad trieda Author bude po vygerovaní a pridaní pár ďalších drobností (vzťahu k entite Book @OneToMany, pomenovanému dopytu listAuthors a konštruktora, ktorý inicializuje kolekciu autorových kníh na prázdny zoznam), vyzerať takto:
package ee.books.db;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "author")
@NamedQueries({
@NamedQuery(
name="listAuthors",
query="SELECT a FROM Author a"
) })
public class Author {
public Author()
{
books = new ArrayList();
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", length = 30)
private String name;
@Column(name = "lastname", length = 30)
private String lastName;
@Column(name = "nationality", length = 3)
private String nationality;
@Column(name = "born_year")
private Integer bornYear;
@OneToMany(mappedBy="author", cascade=CascadeType.REMOVE)
private List<Book> books;
/* gettery a settery pre jednotlive properties */
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastname) {
this.lastName = lastname;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Integer getBornYear() {
return bornYear;
}
public void setBornYear(Integer bornYear) {
this.bornYear = bornYear;
}
public List<Book> getBooks() {
return books;
}
}
V tejto aplikácii využijeme zaujímavé vlastnosti JSF - dáta budeme renderovať do tabuliek, pričom niektoré stĺpce tabuliek budú obsahovať zoznamy (napr. zoznam knižiek príslušného autora). Okrem toho využijeme možnosť odovzdať parametre pri kliknutí na linku v príslušnom riadku tabuľky do metódy v request-scoped beane a umožníme používateľovi vyberať zo zoznamu entít prezentovaných v dropboxe. Na ukážku si pozrime súbor books.xhtml so zoznamom kníh a formulárom na pridanie novej knihy:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:view>
<h:head>
<h:outputStylesheet library="css" name="library-table.css" />
</h:head>
<h:body>
<h3>Books in our little library</h3>
(<i><h:link outcome="index" value="main menu" /></i>)<br/><br/>
<h:dataTable value="#{library.books}" var="book"
styleClass="library-table"
headerClass="library-table-header"
rowClasses="library-table-odd-row,library-table-even-row">
<h:column>
<f:facet name="header">Author</f:facet>
#{book.author.name} #{book.author.lastName}
</h:column>
<h:column>
<f:facet name="header">Title</f:facet>
<b>#{book.title}</b>
</h:column>
<h:column>
<f:facet name="header">Published</f:facet>
#{book.year}
</h:column>
<h:column>
<f:facet name="header">Genre</f:facet>
#{book.genre}
</h:column>
<h:column>
<f:facet name="header">Locality</f:facet>
#{book.locality == null?"---":(book.locality.cabinet.concat("-").concat(book.locality.shelf))}
</h:column>
<h:column>
<f:facet name="header">Borrowed</f:facet>
#{(book.borrowed==1)?"yes":"no"}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:form>
<h:commandLink action="#{library.deleteBook()}" value="Delete">
<f:param name="idBook" value="#{book.id}" />
</h:commandLink>
<h:commandLink action="#{library.toSetLocality()}" value="Set locality">
<f:param name="idBook" value="#{book.id}" />
</h:commandLink>
<h:commandLink action="#{library.borrowOrReturn()}"
value="#{(book.borrowed==1)?'Return':'Borrow'}">
<f:param name="idBook" value="#{book.id}" />
<f:param name="borrowed" value="#{book.borrowed}" />
</h:commandLink>
</h:form>
</h:column>
</h:dataTable>
<br/><br/>
Insert new book: <br/><br/>
<h:form id="newbook">
<table><tr><td>
Author:</td><td>
<h:selectOneMenu value="#{library.bookAuthor}">
<f:selectItems value="#{library.authors}" var="author"
itemLabel="#{author.name} #{author.lastName}" itemValue="#{author.id}" />
</h:selectOneMenu></td></tr><tr><td>
Title:</td>
<td><h:inputText id="booktitle" value="#{library.bookTitle}" required="true">
<f:validateLength maximum="100" />
</h:inputText> <h:message for="booktitle" style = "color:red"/></td></tr><tr><td>
Year published:</td><td><h:inputText id="bookpublished" value="#{library.bookPublished}" required="true">
<f:validateLength minimum="4" maximum="4"/>
</h:inputText> <h:message for="bookpublished" style = "color:red"/></td></tr><tr><td>
Genre:</td><td>
<h:inputText id="bookgenre" value="#{library.genre}" required="true">
<f:validateLength maximum="30"/>
</h:inputText> <h:message for="bookgenre" style="color:red"/></td></tr><tr><td colspan="2">
<h:commandButton value="Insert" action="#{library.insertBook()}"/>
</td></tr></table>
</h:form>
</h:body>
</f:view>
</html>
Čitateľovi odporúčame preštudovať jednotlivé súbory celého nášho cvičného projektu a prečítať si podrobnosti v oficiálnom Java EE tutoriali od Oracle:
Java Platform, Enterprise Edition: The Java EE Tutorial, Persistence.
Zodpovedajúci projekt pre Intellij:
- books:
books-jpa-jakarta-9.zip (a bit outdated version...)
- books:
books-24.zip (a new version updated for Glassfish 7 - recommended) - you may want to install Jakarta EJB and JPA Buddy plugins in IDEA