W dzisiejszym odcinku, stworzymy aplikację webową (a jakże) korzystającą z EJB 3.0 i
JSF. Ktoś może się zapytać po co? Przecież mamy znakomity szkielet na literę S... Owszem mamy, ale może nie znamy, albo może nie potrzebujemy jego możliwości... Różnie to w życiu bywa. Poza tym modułowa budowa, którą narzuca nam podział aplikacji na webową i biznesową wydaje się całkiem rozsądnym rozwiązaniem.
Zobaczmy zatem, co i jak... Do pracy nad aplikacją użyję środowiska
Eclipse 3.3 Europa z zainstalowaną wtyczką
JBoss Tools 2.1.2.GA (ostatnia finalna wersja). Jako środowisko uruchomieniowe posłuży mi kontener
JBoss AS 4.2.0.GA. Bazą danych będzie
MySQL 5.0.
Strukturę aplikacji tworzy archiwum EAR (Enterprise Application Archive) zawierające aplikację EJB (archiwum ejb-jar) oraz aplikację webową (archiwum war).

Ten na pozór skomplikowany podział może okazać się bardzo przydatny w większych, żeby nie powiedzieć korporacyjnych rozwiązaniach. Z warstwy usług zaimplementowanej w aplikacji EJB korzystać może nie tylko nasza aplikacja JSF, ale także inne, niekoniecznie nawet webowe aplikacje.
Stworzona aplikacja, będzie typową aplikacją CRUD, umożliwiającą tworzenie, odczyt, aktualizację i kasowanie pracowników. Potrzebować zatem będziemy 3 projekty. Projekt ejb, jsf oraz zawierający je projekt aplikacji ear.
Zacznijmy od EJB. Klikam
File / New / Other...
i z listy wybieram
EJB Project.

Podaję nazwę projektu
ejbProject i klikam
Next > Na następnej zakładce zaznaczam
EJB module Java i
Java Persistence
klikam 2 razy
Next >, odznaczam
create orm.xml (będę używał adnotacji) i klikam
FinishTworzę w projekcie encję
Employee
package pl.matt.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
@NamedQueries({
@NamedQuery(name = "Employee.findAllOrderByName", query = "FROM Employee e ORDER BY e.lastName, e.firstName")
})
@Entity
public class Employee {
private int id;
private String password;
private String username;
private String firstName;
private String lastName;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(nullable = false)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Column(nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
warstwę dostępu do danych (DAO):
package pl.matt.dao;
import java.util.List;
import pl.matt.model.Employee;
public interface EmployeeDao {
/**
* @return
*/
public List<Employee> getAllOrderByName();
/**
* @param employee
* @return
*/
public Employee create(Employee employee);
/**
* @param employee
*/
public void update(Employee employee);
/**
* @param employeeId
* @return
*/
public Employee load(int employeeId);
/**
* @param employeeId
* @return
*/
public void delete(Employee employee);
}
package pl.matt.dao.impl;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import pl.matt.dao.EmployeeDao;
import pl.matt.model.Employee;
@Stateless
public class JpaEmployeeDao implements EmployeeDao {
@PersistenceContext
private EntityManager entityManager;
@SuppressWarnings("unchecked")
public List<Employee> getAllOrderByName() {
return entityManager.createNamedQuery("Employee.findAllOrderByName").getResultList();
}
public Employee create(Employee employee) {
entityManager.persist(employee);
return employee;
}
public void update(Employee employee) {
entityManager.merge(employee);
}
public Employee load(int employeeId) {
return entityManager.find(Employee.class, employeeId);
}
public void delete(Employee employee) {
entityManager.remove(employee);
}
}
i może trochę na wyrost warstwę usług biznesowych
package pl.matt.service;
import java.util.List;
import pl.matt.model.Employee;
public interface EmployeeService {
/**
* @return
*/
public List<Employee> getAllOrderByName();
/**
* @param employee
* @return
*/
public Employee create(Employee employee);
/**
* @param employee
*/
public void update(Employee employee);
/**
* @param employeeId
* @return
*/
public Employee load(int employeeId);
/**
* @param employeeId
*/
public void delete(int employeeId);
}
package pl.matt.service.impl;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import pl.matt.dao.EmployeeDao;
import pl.matt.model.Employee;
import pl.matt.service.EmployeeService;
@Stateless
public class EmployeeServiceImpl implements EmployeeService {
@EJB
private EmployeeDao employeeDao;
public Employee create(Employee employee) {
return employeeDao.create(employee);
}
public List<Employee> getAllOrderByName() {
return employeeDao.getAllOrderByName();
}
public void update(Employee employee) {
employeeDao.update(employee);
}
public Employee load(int employeeId) {
return employeeDao.load(employeeId);
}
public void delete(int employeeId) {
Employee employee = load(employeeId);
if (employee != null) {
employeeDao.delete(employee);
}
}
}
Ponieważ korzystam z bazy danych za pośrednictwem JPA, nie obejdzie się bez pliku
META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="seam_war_project" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/empAppDatasource</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
potrzebuję też źródła danych (Data Source)
empAppDatasource. Tworzę ją w pliku
empApp-ds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE datasources
PUBLIC "-//JBoss//DTD JBOSS JCA Config 1.5//EN"
"http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd">
<datasources>
<local-tx-datasource>
<jndi-name>empAppDatasource</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/jpabasics?characterEncoding=latin2</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>root</password>
</local-tx-datasource>
</datasources>
który umieszczam w katalogu
server/default/deploy/ JBossa.
Przejdźmy do aplikacji JSF. Tworzę ją wybierając z menu Eclipse
File / New / Other... / JSF Project
Jako środowisko JSF ustawiam JSF 1.2 z
Faceletami, podaję nazwę projektu
jsfModule i klikam
Finish.
W projekcie tworzę prosty szablon stron
WebContent/templates/common.xhtml
<!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>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><ui:insert name="pageTitle">Page Title</ui:insert></title>
<style type="text/css">
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 14px;
}
.header {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 18px;
}
.bottom {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 9px;
text-align: center;
vertical-align: middle;
color: #8E969D;
}
td.column1 {
width: 15%;
}
td.column2 {
width: 25%;
}
td.column3 {
width: 60%;
}
</style>
</head>
<body bgcolor="#ffffff">
<table style="border: 1px solid #CAD6E0" align="center" cellpadding="0"
cellspacing="0" border="0" width="400">
<tbody>
<tr>
<td class="header" height="42" align="center" valign="middle"
width="100%" bgcolor="#E4EBEB"><ui:insert name="pageHeader">Page Header</ui:insert>
</td>
</tr>
<tr>
<td height="1" width="100%" bgcolor="#CAD6E0"></td>
</tr>
<tr>
<td width="100%" colspan="2">
<table width="100%" style="height: 150px" align="left"
cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td align="left" width="800px" valign="middle"><ui:insert
name="body">Page Body</ui:insert></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2" valign="bottom" height="1" width="100%"
bgcolor="#CAD6E0"></td>
</tr>
</tbody>
</table>
</body>
</html>
plik wyświetlający listę pracowników
WebContent/pages/employeeList.xhtml
<!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"
xmlns:c="http://java.sun.com/jstl/core">
<f:loadBundle basename="resources" var="msg" />
<ui:composition template="/templates/common.xhtml">
<ui:define name="pageTitle">Lista pracowników</ui:define>
<ui:define name="pageHeader">Lista pracowników</ui:define>
<ui:define name="body">
<div style="text-align: center"><h:form>
<h:commandLink action="ADD_EMP" value="Dodaj pracownika" />
<br />
<br />
<ui:repeat value="#{employeeBean.employees}" var="emp">
<h:outputLink value="editEmployee.jsf?employeeId=#{emp.id}">
<h:outputText value="#{emp.firstName} #{emp.lastName}" />
</h:outputLink>
<h:outputText value=" " />
<h:commandLink action="#{employeeBean.delete}" value="[usuń]">
<f:param name="toDelete" value="#{emp.id}" />
</h:commandLink>
<br />
</ui:repeat>
</h:form></div>
</ui:define>
</ui:composition>
</html>
oraz plik umożliwiający edycję pojedynczego pracownika
WebContent/pages/editEmployee.xhtml
<!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"
xmlns:c="http://java.sun.com/jstl/core">
<f:loadBundle basename="resources" var="msg" />
<ui:composition template="/templates/common.xhtml">
<ui:define name="pageTitle">Edycja</ui:define>
<ui:define name="pageHeader">Edycja pracownika</ui:define>
<ui:define name="body">
<h:outputLink value="employeeList.jsf">Powrót</h:outputLink>
<h:form id="employeeForm">
<h:inputHidden id="employeeId" value="#{employeeBean.employee.id}" />
<h:panelGrid columns="3" width="800"
columnClasses="column1,column2,column3">
<h:outputText value="Imię" />
<h:inputText value="#{employeeBean.employee.firstName}"
id="firstName" required="true" />
<h:message for="firstName" style="color:red;" />
<h:outputText value="Nazwisko" />
<h:inputText value="#{employeeBean.employee.lastName}" id="lastName"
required="true" />
<h:message for="lastName" style="color:red;" />
<h:outputText value="Login" />
<h:inputText value="#{employeeBean.employee.username}" id="username"
required="true" />
<h:message for="username" style="color:red;" />
<h:outputText value="haslo" />
<h:inputSecret id="password"
value="#{employeeBean.employee.password}" required="true" redisplay="true" />
<h:message for="password" style="color:red;" />
<h:commandButton action="#{employeeBean.save}" value="zapisz" />
</h:panelGrid>
<h:outputLink value="employeeList.jsf">Powrót</h:outputLink>
</h:form>
</ui:define>
</ui:composition>
</html>
Potrzebne będzie też ziarno zarządzane JSF
package pl.matt.view;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.context.FacesContext;
import pl.matt.model.Employee;
import pl.matt.service.EmployeeService;
public class EmployeeBean {
private List<Employee> employees;
private Employee employee;
private Integer employeeId;
@EJB(name="eeProject/EmployeeServiceImpl/local")
private EmployeeService employeeService;
public List<Employee> getEmployees() {
if (employees == null) {
employees = employeeService.getAllOrderByName();
}
return employees;
}
public Employee getEmployee() {
if (employee == null) {
if (employeeId != null) {
employee = employeeService.load(employeeId);
} else {
employee = new Employee();
}
}
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String save() {
if (employee.getId() > 0) {
employeeService.update(employee);
} else {
employeeService.create(employee);
}
employees = null;
return "LIST_EMP";
}
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String delete() {
System.out.println("EmployeeBean.delete()");
Integer id = Integer.valueOf(
FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("toDelete"));
if (id != null) {
employeeService.delete(id);
}
employees = null;
return "LIST_EMP";
}
}
JBoss umieszcza w interfejsie JNDI ziarna EJB korzystając z trochę dziwnej konwencji:
nazwaModułu/klasaZiarna/local|remote stąd konieczność podania parametru
name@EJB(name="eeProject/EmployeeServiceImpl/local")
przy adnotacji @EJB.
Jak będzie wyglądała sytuacja w momencie, kiedy ziarno EJB będzie implementowało kilka interfejsów zdalnych lub lokalnych? Niestety nie mam pojęca.
Do kompletu brakuje jeszcze pliku
WebContent/WEB-INF/faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<managed-bean>
<managed-bean-name>employeeBean</managed-bean-name>
<managed-bean-class>pl.matt.view.EmployeeBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>employeeId</property-name>
<value>#{param.employeeId}</value>
</managed-property>
</managed-bean>
<navigation-rule>
<navigation-case>
<from-outcome>ADD_EMP</from-outcome>
<to-view-id>/pages/editEmployee.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>LIST_EMP</from-outcome>
<to-view-id>/pages/employeeList.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
oraz
WebContent/WEB-INF/web.xml
<?xml version="1.0"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<description>Facelets StarterKit</description>
<display-name>jsfModule</display-name>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>facelets.REFRESH_PERIOD</param-name>
<param-value>2</param-value>
</context-param>
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
</web-app>
Aplikacja JSF gotowa. Aplikacja EJB gotowa. Czas je wyswatać i pożenić. Pomoże nam w tym projekt EAR. Z menu kontekstowego Eclipse wybieram zatem
File / New / Other... / Enterprise Application Project.

Podaję nazwę projektu
eeProject klikam 2 razy
Next > i wybieram oba stworzone uprzednio moduły. Będą to składowe naszej aplikacji. Dodatkowo zaznaczam opcję
Generate Deployment Descriptor i klikam
Finish.

W tym projekcie znajduje się tylko plik
EarContent/META-INF/application.xml.
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" id="Application_ID" version="5">
<module>
<ejb>ejbProject.jar</ejb>
</module>
<module>
<web>
<web-uri>jsfModule.war</web-uri>
<context-root>jsfModule</context-root>
</web>
</module>
</application>
Plik ten opisuje moduły naszej aplikacji. Moduł webowy
jsfModule i moduł EJB
ejbProject.jar. Wdrożenie aplikacji pozostawiam wtyczce JBoss Tools. Startuję więc serwer i... aplikacja działa.

Kod źródłowy powyższej aplikacji dostępny jest
tutaj.
Cóż, pora jechać na narty...