W ramach zajęć zostaną przedstawione zagadnienia związane z przetwarzaniem danych przechowywanych
w bazach danych. Na początek omówiona zostanie usługa JNDI (Java Naming and Directory Interface) umożliwiająca
mapowanie połączenia z bazą danych w katalogu usługi JNDI. Następnie zostaną przedstawione zagadnienia związane
z tworzeniem pól połączeń (connection pools) do bazy danych.
Centralnym zagadniem laboratorium będzie przedstawienie technologii JPA (Java Persistance API) umożliwiającej odwzorowanie
obiektów Javy na odpowiednie rekordy w tablicach relacyjnej bazy danych. Omówione zostaną następujące zaganienia:
interfejs programistyczny JPA ( zawierający zarządcę trwałości ), następnie encje odwzorowujące obiekty bazodanowe
oraz tworzenie zapytań zgodnie z Java Persistence Query Language.
Skrypty opracowane w ramach zajęć.
Odczyt danych z relacyjnej bazy danych (RBD) w skrypcie JSP z wykorzystaniem JNDI.
Odczyt danych z RBD w skrypcie JSF z wykorzystaniem JPA (zarządzanie na poziomie kontenera).
Prosta aplikacja CRUD w technologii JSF zrealizowana z wykorzystaniem JPA (zarządzanie przez kontener).
Realizacja aplikacji CRUD w technologii JSF i JPA zarządzane w ramach aplikacji.
Usługa JNDI i skrypt JSP
W ramach technologii JEE dostępna jest technologia JNDI, której celem jest dostarczenie jednolitego interfejsu
w ramach rozprosznej aplikacji do innych komponentów i zasobów tj. bazy danych, sesje wiadomości e-mail, obiekty JMS ( Java
Message Service) czy serwis LDAP [1,2,3]. W ramach interfejsu JNDI każdy obiekt zasobu jest identyfikowany przez unikalna nazwę, która
jest czytelna dla programisty, dostęp poprzez JDBC do bazy danych określny jest np. przez nazwę np. jdbc/bazadb. Obiekt zasobu i jego
nazwa JNDI są powiązane przez usługi katalogowe. W ramach serwera aplikacyjnego za obsługę JNDI odpowiada odpowieni serwis natomiast
w ramach kontenera Web funkcjonalność JNDI uzyskujemy poprzez odpowiedni wpis w plikach konfigurujących serwera. Architektura
interfejsu JNDI przedstawiona została na rys.1 ( rysunek z dokumentacji Oracle ). W naszej aplikacji
wykorzystamy serwer aplikacyjny Apache TomEE a odpowiednie wpisy zostaną umieszczone w plikach konfiguracyjnych context.xml,
resources.xml i web.xml.
Rys.1. Architektura interfejsu JNDI (Oracle).
W ramach laboratorium opracujemy kilka przykładów połączenia z bazą danych w ramach aplikacji tworzonych w ramach środowiska JEE w kontenerze
WEB Apache TomEE:
konfiguracja usługi JNDI na serwerze aplikacyjnym Apache TomEE,
testowe połączenie z bazą danych z wykorzystaniem skrpyptu JSP i usługi JNDI,
skrypt JSF łączący się z bazą danych poprzez JDBC i JNDI,
skrypt JSF łączący się z bazą danych poprzez JDBC, JNDI i interfejs JPA,
prosta aplikacja CRUD z wykorzystaniem JSF i JPA.
W środowisku IntelliJ tworzymy nowy projekt ZTI_Lab03 typu Jakarta Enterprise ( rys.2 i rys.3 ).
Rys.2. Konfiguracja projektu w ramach IDE IntelliJ.Rys.3. Konfiguracja projektu w ramach IDE IntelliJ.
Do realizacji zadań wykorzystujących JNDI i skrypt JSP przygotujemy odpowiednie środowisko uruchomieniowe. Na początek
tworzymy plik context.xml (tworzymy plik typu xml) w katalogu webapp/META-INF i umieszczamy w nim poniższą zawartość.
[[dburl]] - jdbc:postgresql://host:5432/dbname naszej bazy danych na serwerze Pascal (jak na poprzednich laboratoriach),
[[dbuser]] - użytkownika bazy danych user,
[[dbpassword]] - hasło do naszej bazy danych.
Do poprawnej funkcjonalności naszego projektu wymagane jest jeszcze umieszczenie biblioteki obsługującej bazę PostgreSQL w projekcie
postgresql-42.7.5.jar, które dołączamy do pliku konfiguracyjnego menadżera Maven.
Na początek przetestujemy nasze połączenie z relacyjną bazą danych z wykorzystanie skryptu JSP i JSTL. Tworzymy
skrypt JSP TestJNDI.jsp z poniższą zawartością.
Sprawdzamy poprawność działania naszego skryptu http://localhost:8080/zti_lab03 ... /TestJNDI.jsp. Poprawnie przygotowane
środowisko, połączenie z bazą danych oraz skrypt powinny umożliwić otrzymanie wyniku przedstawionego na rys.4.
Rys.4. Test interfejsu JNDI do bazy dany w skrypcie JSP.
Framework JSF i technologia JPA
W ramach tego zadania opracujemy prosty serwis w technologii JSF odczytujący dane z bazy danych na serwerze Pascal
z wykorzystaniem interfesju JPA.
Na początek przygotujemy środowisko uruchomieniowe dla skryptów JSF. Wymagany jest utworzenie skryptu faces-config.xml
w katalogu WEB-INF oraz modyfikacja skryptu web.xml.
Tworzymy plik faces-config.xml (można skorzystać z kreatora w ramach narzędzia IDEA).
W kolejnym punkcie przygotowujemy plik konfiguracyjny persistence.xml
zawierający informację na temat źródła danych i encji wykorzystywanej w projekcie. W ramach tego zadania zostanie
opracowana jedna encja person odwzorowująca tabelę PERSON w bazie danych.
Plik persistence.xml zawiera informację o bazie danych, informację o implemantacji JPA w ramach technologii JTA. Plik umieszczamy w ramach IDE
w katalogu resources/META-INF (standardowo umieszczany również przez IDE). Zawartość pliku przedstawiona została poniżej. Dostęp do bazy danych zrealizowany
został poprzez interfejs JNDI ("jdbc/postgres"), którego parametry zostały ustawione w pliku resources.xml.
package zti.model;
import java.io.Serializable;
import jakarta.persistence.*;
/**
* The persistent class for the person database table.
*
*/
@Entity (name="person")
@Table (name="person", schema="public")
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String city;
private String email;
private String fname;
private String lname;
private String tel;
public Person() {
}
@Id
//@SequenceGenerator(name="PERSON_ID_GENERATOR" )
//@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PERSON_ID_GENERATOR")
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCity() {
return this.city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFname() {
return this.fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return this.lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public String getTel() {
return this.tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}
Obsługa funkcjonalności JPA i połaczenie z bazą danych zostanie zrealizowana w klasie PersonRepository.java. Klasa tworzy manadżer trwałości
na podstawie pliku persistence.xml.
<!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:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Test - view JPA</title>
</head>
<body>
<div id="content">
<h1>#{msg.titleFormJPA}</h1>
<h2>#{msg.subtitleFormJPA}</h2>
<h3>#{msg.titleFormJPAPostgresql}</h3>
<h:form>
<h:dataTable value="#{servJPABean.people}" var="record1" border="1">
<h:column>
<!-- kolumna zawiera nazwiska osob -->
<f:facet name="header">
<h:outputText value="#{msg.Lastname}" />
</f:facet>
<h:outputText value="#{record1.lname}" />
</h:column>
<h:column>
<!-- kolumna zawiera imiona osob -->
<f:facet name="header">
<h:outputText value="#{msg.Firstname}" />
</f:facet>
<h:outputText value="#{record1.fname}" />
</h:column>
<h:column>
<!-- kolumna zawiera e-mail osob -->
<f:facet name="header">
<h:outputText value="#{msg.Email}" />
</f:facet>
<h:outputText value="#{record1.email}" />
</h:column>
</h:dataTable>
</h:form>
</div>
</body>
</html>
Uzupełniamy zawartość pliku messages.properties (resources/data) i sprawdzamy poprawność działania skryptów (rys.5).
titleFormJPA=Prezentacja interfejsu JPA
subtitleFormJPA=Parametry polaczenia w usludze JNDI
titleFormJPAPostgresql=Odczyt danych z bazy danych PostgreSQL
Firstname=Imie
Lastname=Nazwisko
City=Miasto
Email=E-mail
Tel=Telefon
Rys.5. Rekordy w bazie danych PostgreSQL (interfejs JPA).
JPA - aplikacja CRUD
W ramach tego zadania zapoznamy się z przetwarzaniem zapytań do bazy danych poprzez intefejs JPA wykorzytując
metody menadżera trwałości tj. find(), merge(), flush(), remove() czy persist(). Zapytania do bazy danych
bedą realizowane w ramach transakcji nadzorowanych przez serwer aplikacyjny (JTA). Przetwarzanie zapytań przez JPA będzie realizowane
w ramach wcześniej utworzonej klasy PersonRepository, którą uzupełnimy o dodatkowe funkcjonalności.
dodanie rekordu do bazy danych - metoda persist(),
modyfikacja rekordu w bazie danych - merge(),
usunięcie rekordu z bazy danych - remove(),
pobranie rekordu z bazy danych - find(),
synchronizacja pomiędzy bazą danych a kontekstem trwałości - flush(),
Poniżej zawarta jest implementacja przedstawionych powyżej funkcjonalności w ramach klasy PersonRepository.java (dodatkowe metody).
public void savePerson( Person entity) {
System.out.println("[ConnJPA] Save entity - " + entity.getLname() );
entityManager.persist(entity);
entityManager.flush();
}
public void updatePerson(Person entity) {
entityManager.merge(entity);
}
public void deletePerson(Integer id ) {
Person p = entityManager.find(Person.class, id);
entityManager.remove(p);
entityManager.flush();
}
public Person findPerson(Person entity) {
return entityManager.find(Person.class, entity);
}
Kolejną klasą, którą uzupełnimy jest klasa zarządzana ServJSF.java obsłująca JSF.
Poniżej zawartość klasy po dodaniu dodatkowych metod.
package zti.lab3;
import java.io.Serializable;
import java.util.List;
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import zti.model.Person;
import zti.model.PersonRepository;
@Named ("servJPABean")
@SessionScoped
public class ServJSF implements Serializable {
private Person editPerson;
private Person viewPerson;
private Person addPerson = new Person();
@Inject
PersonRepository personRepository ;
public List<Person> getPeople() {
return personRepository.getAllPersons();
}
public String delPerson(Integer id) {
personRepository.deletePerson(id);
return "allRecPost" ;
}
public Person getEditPerson() {
return editPerson;
}
public Person getAddPerson() {
return addPerson;
}
public Person getViewPerson() {
return viewPerson;
}
public String selPerson(Person entity) {
viewPerson = copy(entity);
return "viewRecPost" ;
}
public String updPerson(Person entity) {
editPerson = copy(entity);
return "updRecPost" ;
}
public String updatePerson() {
personRepository.updatePerson(editPerson);
editPerson = new Person();
return "allRecPost";
}
public String savePerson() {
personRepository.savePerson(addPerson);
addPerson = new Person();
return "allRecPost";
}
private Person copy(Person entity) {
Person p = new Person();
p.setId(entity.getId());
p.setFname(entity.getFname());
p.setLname(entity.getLname());
p.setCity(entity.getCity());
p.setEmail(entity.getEmail());
p.setTel(entity.getTel());
return p ;
}
}
Do zamknięcia zadania pozostała jeszcze realizacja serwisu WWW. Serwis zostanie zrealizowany
w oparciu o doświadczenia z zadania realizowanego na laboratorium z JSF. Na początek
utworzymy odpowiednie szablony a następnie kolejne strony obsługujące wymaganą funkcjonalność serwisu.
Szablon główny aplikacji masterTemplate.xhtml umieszczony w katalogu templates.
Uzupełniamy brakująe wpisy w pliku messages.properties.
siteTitleCRUD=ZTI2022 - JPA & CRUD
welcomePageCRUD=Aplikacja CRUD z wykorzystaniem JPA i JNDI
headCRUDappl=Aplikacja CRUD + JPA
footerCRUDappl=ZTI 2022, Lab03 JPA, Apache TomEE
linkAllRecPostgresqlSidebar=Lista rekordow w PostgreSQL
linkAddRecPostgresqlSidebar=Nowy rekord do bazy PostgreSQL
titleFormList=Lista uzytkownikow.
actionFormList=Operacje
viewLinkFormList=Podglad
editLinkFormList=Popraw
delLinkFormList=Usun
titleFormAdd=Edycja danych uzytkownika.
saveLinkFormAdd=Zapisz
titleFormUpdate=Edycja danych uzytkownika.
saveLinkFormUpdate=Popraw
titleFormView=Dane uzytkownika.
viewFormCRUD=Widok
editFormCRUD=Edycja
delFormCRUD=Usuwanie
Dodajemy funkcjonalność obsługującą obsługę błędów w formularzu wprowadzającym do bazy danych. Na początek wpis do
pliku faces-config.xml i odpowiednie wpisy w pliku mess-errors.properties.
AddFirstname=Podanie imienia jest wymagane
AddLastname=Podanie nazwiska jest wymagane
AddEmail=Podanie e-mail jest wymagane
Sprawdzamy poprawność działania aplikacji http://localhost:8080/zti_lab03 ... /viewCRUD.jsf (rys.6). Na
rys.7 przedstawiono strukturę plików opracowanego projektu.
Rys.6. Opracowane zadanie CRUD wykorzystujące interfejs JPA.Rys.7. Struktura plików w opracowanym projekcie.
JPA - aplikacja CRUD w kontenerze Docker
Uruchomienie aplikacji Jakarta EE w kontenerze Docker z wykorzystaniem JSF i JPA możliwe jest poprzez przygotowanie aplikacji
zarządzającej JPA w ramach aplikacji lub poprzez odpowiednie przygotowanie serwera aplikacyjnego. W ramach naszego zadania
zrealizujemy zadanie przygotowując aplikację, która sama zarządza warstwą trwałości JPA. Do realizacji zadania wykorzystamy projekt
zrealizowany w poprzednim zadaniu zmieniejąc zawartość kilku plików.
Wdrożenie aplikacji w środowisku systemu Linux i Windows.
Uwaga, realizacja tego zadania jest inna dla serwera aplikacyjnego TomEE uruchamianego w środowisku Windows a inna w środowisku Linux.
Przedstawione poniżej skrypty działają poprawnie w środowisku systemu Linux. Nie działają na serwerze aplikacyjnym TomEE uruchomionym w środowisku
systemu Windows. Realizacja zadania w systemie Windows wymaga zmian w pliku persistence.xml i udostępnienia pliku resources.xml.
Tworzymy nowy projekt zgodnie z przedstawionymi poniżej zależnościami rys.8 i 9.
Rys.8. Konfiguracja projektu w ramach IDE IntelliJ.Rys.9. Konfiguracja projektu w ramach IDE IntelliJ.
Do nowego projektu przekopiujemy wszystkie pliki z projektu zrealizowanego w poprzednim zadaniu, bez pliku resources.xml
oraz podmieniając zawartość plików persistence.xml i PersonRepository.java.
W ramach pliku pliku persistence.xml została zamieszczona informacja, że aplikacja samodzielnie będzie zarządzała
warstwą trwałości, parametr transaction-type="RESOURCE_LOCAL" oraz dane wymagane do podłączenia do bazy danych.
W ramach pliku PersonRepository.java tworzymy lokalnego zarządcę trwałości EntityManager oraz wdrażamy obsługę transakcji
w ramach menadżera trwałości (kontener jej nie obsługuje).
package zti.model;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
@Named
//@Stateless
public class PersonRepository implements Serializable {
private EntityManager entityManager;
private EntityTransaction tx;
private EntityManagerFactory emf;
public PersonRepository() {
emf = Persistence.createEntityManagerFactory("PU_Postgresql");
entityManager = emf.createEntityManager();
tx = entityManager.getTransaction();
}
public List<Person> getAllPersons() {
List<Person> people = null;
try {
tx.begin();
people = entityManager.createQuery("select p from person p", Person.class)
.getResultList();
tx.commit();
} catch (Exception e) {
System.out.println("Failed !!! " + e.getMessage());
}
return people;
}
public void savePerson(Person entity) {
try {
tx.begin();
System.out.println("[ServJSF] Save entity - " + entity.getLname());
entityManager.persist(entity);
entityManager.flush();
tx.commit();
} catch (Exception e) {
System.out.println("Failed !!! " + e.getMessage());
}
}
public void updatePerson(Person entity) {
try {
tx.begin();
entityManager.merge(entity);
tx.commit();
} catch (Exception e) {
System.out.println("Failed !!! " + e.getMessage());
}
}
public void deletePerson(Integer id) {
try {
tx.begin();
Person p = entityManager.find(Person.class, id);
entityManager.remove(p);
entityManager.flush();
tx.commit();
} catch (Exception e) {
System.out.println("Failed !!! " + e.getMessage());
}
}
public Person findPerson(Person entity) {
Person p = null;
try {
tx.begin();
p = entityManager.find(Person.class, entity);
tx.commit();
} catch (Exception e) {
System.out.println("Failed !!! " + e.getMessage());
}
return p;
}
}
Uruchamiamy aplikację w ramach IDEA oraz tworzymy plik zti_lab03c.war, który umieszczamy w katalogu webapps serwera
Apache TomEE w kontenerze. Poprawnie przygotowana aplikacja będzie prezentowała ekran zaprezentowany na rys. 10.
Rys.10. Aplikacja CRUD uruchomiona w kontenerze Docker.
Zaliczenie laboratorium
Linki do dodatkowych materiałów (dostęp na dzień 26.03.2025)