Java EE nagyongyorstalpaló
A következőkben megnézzük egy olyan, egyszerű Java Enterprise Alkalmazás elkészítését, ami azokat a technológiákat használja, mint az njCMS. Azoknak akik ebből akarják a Java EE-t megérteni: ez a leírás hiányos és szinte biztos, hogy tartalmaz tárgyi tévedéseket.
Előkészületek
Hello Enterprise World
Hozz létre egy Enterprise Application-t, default beállításokkal. (Ha szervernek Tomcat-et ajánl, akkor állítsd ét Glassfish V2-re. A Tomcat a Java EE-nek csak a webes rész-API-ját implementálja.)
Amint láthatod 3 project jött létre:
- Tutorial
- Ez az egész alkalmazás főprojektje, összefogja a következő kettőt.
- Tutorial-ejb
- Ebben kell megvalósítani az üzleti logikát
- Tutorial-war
- Ebben kell megvalósítani a webes user interfészt, servletekkel, jsp-vel, stb. Ez olyan mint a tomcat-ből is ismert sima webes projektek, azzal a különbséggel, hogy a Tutorial-ejb szolgáltatásait használja fel.
Az így készülő alkalmazások a háromrétegű architektúrát fogják követni: adatbázis, üzleti logika, user interfész. Az üzleti logika végez minden adatbázis-műveletet, tehát elfedi az adatbázist az user-interfész elől, felel az adatbázisban lévő adatok konzisztenciájáért. A user interfész tehát a felhasználóval és az üzleti logikával lesz közvetlen kapcsolatban, másszóval nem lesz benne például közvetlen adatbázis-manipuláció.
Session beanek definíciója
Első körben még az adatbázissal ne foglalkozzunk, csak nézzünk egy példát a user interfész - üzleti logika (másszóval war-ejb) együttműködésre! Java EE-ben az üzleti logika Java Bean nevű komponensekből áll, amik szolgáltatásokat nyújtanak kifelé. Ezeket használhatja a user interfész. Első java beanünk az Állapotnélküli Session Bean. Ez tulajdonképpen egy java osztály, aminek nincsenek mezői, a metódusai pedig a kívülről elérhető szolgáltatások, azaz az Üzleti metódusok. Valahogy így néz ki:
@Stateless
public class TestBean implements TestLocal {
public String helloMethod(String name) {
return "hello "+name+"!";
}
}
Két megjegszés: az annotációk a Java 1.5-ben jelentek meg. Metaadatokat lehet velük kapcsolani: osztályhoz, osztály-mezőhöz vagy metódushoz. Ez a session bean önmagában még nem életképes, mert nem adtuk meg az interfészét. Ez egy Java interface lesz, és ezen keresztül lehet majd elérni a beanen kívülről is a szolgáltatásait. Így néz ki:
@Local
public interface TestLocal {
String helloMethod(String name);
}
A @Local annotáció azt jelenti, hogy ezen az interfészen keresztül csak azonos JVM-ben futó programból lehet ezt a Session Bean-t elérni. A lényeg: az njCMS-ben mindig ezt használjuk.
Session beanek használata
Egy session bean-t például egy servletből lehet meghívni, így:
public class Demo1 extends HttpServlet {
@EJB TestLocal testLocal;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<body>");
out.println(testLocal.helloMethod("Enterprise World"));
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Itt a @EJB annotáción van a lényeg, és azon, hogy a session bean interfészére kell hivatkozni, nem pedig az implementációjára. (És a konténer - esetünkben a GlassFish - majd megkeresi ehhez az interfészhez az EJB-ben azt osztályt, amelyik implementálja.)
Entity Beanek
Az Entity Beanek nem szolgáltatást nyújtanak, hanem adatot tárolnak. Ráadásul perzisztens, azaz maradandó módon. Egyszerűbben: egy Entity Bean valójában egy adatbázistábla egy sorának felel meg. Így lehet csinálni egyet:
@Entity
public class Semmi {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private Long val;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getVal() {
return val;
}
public void setVal(Long val) {
this.val = val;
}
}
Ez például a "Semmi" nevű adatbázistáblát írja le. Ennek három oszlopa lesz, Id, Name és Value. Valamilyen egyedi azonosítónak mindig kell lennie, és azt mindig a @Id annotáció jelzi. A "Semmi" nevű adatbázistábla sorai pedig ennek az osztálynak a példányainak felelnek meg. A táblát manipulálni csak az üzleti logikában, azaz az ejb projekt fájljaiban lehet. Ráadásul azon belül is csak bizonyos helyeken. Például session beanekben:
@Local
public interface TestLocal {
String helloMethod(String name);
void createSemmi(String name, Long value);
List<Semmi> allSemmi();
}
@Stateless
public class TestBean implements TestLocal {
@PersistenceContext
EntityManager em;
public String helloMethod(String name) {
return "hello "+name+"!";
}
public void createSemmi(String name, Long value) {
Semmi s = new Semmi();
s.setName(name);
s.setVal(value);
em.persist(s);
}
public List<Semmi> allSemmi() {
return em.createNamedQuery("allSemmiQuery").getResultList();
}
}
Ez természetesen két fájl. Az entitásokat egy EntityManager típusú objektum szolgáltatásaival tudjuk manipulálni. Ez ugyan egy tagváltozója az állapotnélküli Session Bean-nek, de az előtte lévő annotáció jelzi, hogy az értékét a konténer fogja beállítani. Paraméterként sose adjuk át, mindig helyileg definiáljuk!
Az allSemmi metódus visszatérési értéke az adatbázisban lévő összes Semmi objektum listája. (Ez kiadható a web-rétegnek, de ekkor a lista elemei lecsatolódnak, vagyis a rajtuk végzett módosítások már nem kerülnek vissza az adatbázisba.) Visszatérve a metódus tartalmához, ahhoz, hogy ez működjön, definiálni kell még az "allSemmiQuery" nevű lekérdezést, amit itt most meghívunk. Ennek a nyelve az SQL-hez hasonló EJB-QL és a definíció módja:
@NamedQuery(name="allSemmiQuery", query="SELECT s FROM Semmi s")
Ha még emlékszel, akkor az elején azt mondtam, hogy annotációt csak osztály, metódus, vagy mező elé lehet rakni, másszóval mindig kell valami amit annotál. A NamedQuery-ket entitások elé kell rakni. (De nem számít melyik elé.)
@Emtity
@NamedQuery(name="allSemmiQuery", query="SELECT s FROM Semmi s")
class Semmi {
...
Mindez egy servletből egyszerűen meghívható. És ez a példa egyben bemutatja az njCMS-ben (és széleskörűen) használt Servlet-JSP konvenciót:
public class Demo2 extends HttpServlet {
@EJB TestLocal testLocal;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
String value0 = request.getParameter("value");
if (name != null && value0 != null) {
Long value = Long.parseLong(value0);
testLocal.createSemmi(name, value);
}
List<Semmi> sl = testLocal.allSemmi();
request.setAttribute("sl", sl);
request.getRequestDispatcher("/demo2.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Tehát ez a Servlet ha kap megfelelő paramétereket akkor létrehoz egy Semmit, utána pedig minden esetben lekéri a Semmik listáját, majd lefuttatja az utolsó két, misztikus sorát. Ezek valójában csak a kimenő html-t generálják le a meglévő adatok birtokában, néhány out.println() és egy ciklus ugyanezt valósítaná meg. Ebben az esetben viszont egy jsp fájl végzi a html generálását, ami szebb. (JSP-ben körülményes session-beant hívni. És ez azért is jó, mert még egy szinten szétválasztjuk a megjelenítést és az adatfeldolgozást.) A JSP-ben még azt figyeld meg, hogy kerüljük a <% ... %> típusú beszúrt kódokat:
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<form action="Demo2" method="post">
name: <input type="text" name="name"><br />
value: <input type="text" name="value"><br />
<input type="submit" value="Létrehoz">
</form>
<c:forEach items="${sl}" var="semmi">
name= ${semmi.name}, value= ${semmi.val} <br />
</c:forEach>
</body>
</html>
Az sl változó azért érhető el, mert beállítottuk setAttribute-el.