Celem tego wpisu jest pokazanie w jaki sposób stworzyć dynamiczną prostą aplikację WWW wykorzystującą technologię Java Servlets pobierającą dane z lokalnie dostępnej bazy danych Oracle 10g XE z wykorzystaniem JPA. Całość ma obsługiwać Glassfish v3.
Struktura relacyjna w bazie danych jest bardzo prosta, ale wystarczająca do zapoznania z JPA – składa się z tabeli zawierającej dane studentów takie jak imię, nazwisko i z z tabeli opisującej grupy dziekańskie. Obie tabele powiązane są relacją wiele-do-jednego, także grupa może składać się z wielu studentów, natomiast każdy student przynależy do dokładnie 1 grupy.

Na wstępie zaznaczam, że jestem początkujący w tej dziedzinie. Jeśli ktoś bardziej doświadczony, znajdzie jakieś nieścisłości lub błędy to proszę o pozostawienie komentarza. Chętnie poprawię i przy okazji czegoś się nauczę.
Opisana wyżej struktura bazy danych jest reprezentowana przez dwie encje połączone relacją i wygląda to następująco:

jpa_entities

Java Persistence Api (JPA) jest wykorzystywane do mapowania encji zapisanych w bazie danych (wiersze tabeli) na ich obiektową reprezentację również nazywaną encją. Zbieżność terminów może powodować konsternację dlatego w dalszej części wpisu przez pojęcie encja należy rozumieć tylko odpowiednio opisaną klasę w języku Java.
W ogólnym wypadku każda klasa spełniająca wymogi JavaBean tj. posiadająca bezparametrowy konstruktor oraz metody get i set dla pól może zostać przekształcona w encję. Dla przykładu, Grupa może zostać opisana przez taki komponent:

package eu.doniec.piotr.bean

public class Grupa {
    private Long id;

    private String nazwa;
    private String opis;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getNazwa() { return nazwa; }
    public void setNazwa(String nazwa) { this.nazwa = nazwa; }
    public String getOpis() { return opis; }
    public void setOpis(String opis) { this.opis = opis; }
}

Modyfikacja do postaci encji, możliwej do wykorzystania przez JPA do obsługi danych zawartych w tabeli Grupy polega na opisaniu klasy metainformacjami – adnotacjami z pakietu javax.persistence. Gotowa klasa przedstawia się następująco:

package eu.doniec.piotr.persistence;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@NamedQuery(
    name="findAllGroups",
    query="SELECT g FROM Grupa g"
)
@Table(name="GRUPY")
public class Grupa implements Serializable {

    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String nazwa;
    private String opis;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getNazwa() { return nazwa; }
    public void setNazwa(String nazwa) { this.nazwa = nazwa; }
    public String getOpis() { return opis; }
    public void setOpis(String opis) { this.opis = opis; }
}

Podobnie sytuacja wygląda z klasą Studenta, choć tu sytuacja jest trochę inna. Struktura bazy danych wymaga aby tabela STUDENCI posiadała ograniczenie FOREIGN_KEY na dodatkowe pole GRUPA_ID. W takiej sytuacji w ramach encji Student umieszczamy odwołanie do encji Grupa. Właściwość ta jest dodatkowo oznaczona adnotacją @ManyToOne oznaczającą właśnie że wielu studentów może przynależeć do jednej grupy.

package eu.doniec.piotr.persistence;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;

@Entity
@NamedQuery(
    name="findAllStudents",
    query="SELECT s FROM Student s"
)
@Table(name="STUDENCI")
public class Student implements Serializable {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long nr_indeksu;

    private String imie;
    private String nazwisko;

    @ManyToOne
    private Grupa grupa;

    public Long getNr_indeksu() { return nr_indeksu; }
    public void setNr_indeksu(Long nr_indeksu) { this.nr_indeksu=nr_indeksu; }
    public String getImie() { return imie; }
    public void setImie(String imie) { this.imie = imie; }
    public String getNazwisko() { return nazwisko; }
    public void setNazwisko(String nazwisko) { this.nazwisko = nazwisko; }
    public Grupa getGrupa() { return grupa; }
    public void setGrupa(Grupa grupa) { this.grupa = grupa; }
}

Encje są zdefiniowane, pozostało jeszcze je wykorzystać. W tym celu powstanie servlet, który wyświetli nr indeksu, nazwisk i przydział studenta do grupy. W celu wykorzystania stworzonych obiektów trzeba utworzyć obiekt EntityManager’a, a raczej „wstrzyknąć” go do aplikacji – uzyskuje się to annotacją @PersistenceContext. Utworzony w ten sposób EntityManager jest zarządzany przez kontener aplikacji tzw. container-managed. Sensowne wydaje się wstrzyknięcie go w klasie implementującej ServletContextListener i zapisanie w kontekście servletu, w ten sposób łatwo będzie się do niego dostać:

package eu.doniec.piotr.listeners;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.ServletContext;

import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;

@WebListener()
public class ContextListener implements ServletContextListener {

    @PersistenceContext
    private EntityManager em;

    private ServletContext context;

    public void contextInitialized(ServletContextEvent sce) {
        context = sce.getServletContext();
        context.setAttribute("EntityManager", em);
    }

    public void contextDestroyed(ServletContextEvent sce) {
        context.removeAttribute("EntityManager");
    }
}

Na tym etapie, pozostało już tylko stworzenie serwletu i konfiguracja połączenia z bazą danych. Ostatni, prymitywny fragment kodu wypisuje na ekran nr indeksu, nazwisko i nazwę grupy do której zapisany jest student.

package eu.doniec.piotr.servlets;

import eu.doniec.piotr.persistence.Student;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import javax.persistence.EntityManager;
import javax.persistence.Query;

@WebServlet(name="studenci", urlPatterns={"/studenci"})
public class Studenci extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            EntityManager em = (EntityManager)getServletContext().getAttribute("EntityManager");
            Query q = em.createNamedQuery("findAllStudents");

            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet studenci</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Servlet studenci at " + request.getContextPath () + "</h1>");

            out.println("<table>");
            out.println("<tr><th>Numer indeksu</th><th>Nazwisko</th><th>Grupa</th></tr>");

            List studenci =  q.getResultList();
            for (Iterator iter = studenci.iterator(); iter.hasNext();) {
                Student s = (Student) iter.next();
                out.println("<tr>");
                out.println("<td>" + s.getNr_indeksu() +"</td>");
                out.println("<td>" + s.getNazwisko() + "</td>");
                out.println("<td>" + s.getGrupa().getNazwa() +"</td>");
                out.println("</tr>");
            }

            out.println("</body>");
            out.println("</html>");
        } finally {
            out.close();
        }
    } 

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
}

Plik persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="baza-danych-nauczyciela-pu" transaction-type="JTA">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <jta-data-source>jdbc/bazanauczyciela</jta-data-source>
  </persistence-unit>
</persistence>

Sposób stworzenie źródła danych dla Oracle pod Glassfish wystarczająco dobrze opisany jest tu: http://technology.amis.nl/blog/1276/configuring-a-oracle-datasource-on-glassfish.