Você está na página 1de 12

Tabela CRUD simples com JSF 2.

3 e PrimeFaces
Fonte: Tabela CRUD simples com JSF 2.3 e PrimeFaces - rieckpil (rieckpil-de.translate.goog)

Siga @rieckpil no Twitter


Como minha última postagem no blog sobre uma breve introdução ao JavaServer Faces 2.3 recebeu muita atenção (meu tweet foi até retuitado pela página do Twitter do Java
EE Guardian ( @javaee_guardian ), quero continuar a escrever sobre JSF. Desta vez, sobre aplicativos CRUD com JSF e PrimeFaces.
Hoje abordarei um dos casos de uso mais comuns para o desenvolvimento de aplicativos baseados na Web: exibir/ inserir / atualizar / excluir dados em uma tabela
simples. Fazer isso com um aplicativo JavaScript de página única levaria à escrita de muito código para validação , transferência de dados AJAX , manipulação de
dados e tratamento correto de erros . No lado do servidor, você provavelmente oferecerá uma API REST para este caso de uso e usará um banco de dados para
armazenamento persistente. Com Java EE muitas dessas tarefas já estão definidas em uma especificação e só precisamos utilizá-las.
Vamos ver como podemos aproveitar ao máximo JSF , Bean Validation e JPA para este caso de uso. Para dar um breve exemplo, finjo que estamos construindo
um pequeno aplicativo CRM baseado na web para gerenciar nossos clientes. Usarei Java EE 8, aprimorarei a página JSF 2.3 com PrimeFaces 8.0 , implantarei em
um Payara 5.201 como um contêiner docker e usarei o banco de dados na memória padrão do Payara H2 . A página final terá a seguinte aparência:
Configuração do projeto JSF usando PrimeFaces
Começando com o pom.xml, há algumas mudanças em comparação com o exemplo 'Hello World' do JSF :

<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>de.rieckpil.blog</groupId>
<artifactId>simple-crud-table-with-jsf-and-primefaces</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>

<repositories>
<repository>
<id>prime-repo</id>
<name>Prime Repo</name>
<url>http://repository.primefaces.org</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>8.0</version>
</dependency>

<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>all-themes</artifactId>
<version>1.0.10</version>
</dependency>
</dependencies>

<build>
<finalName>crud-table-jsf-primefaces</finalName>
</build>
</project>
Para elementos JSF mais convenientes, estou usando a biblioteca PrimeFaces e sua biblioteca ' all-theme' para alterar o tema padrão posteriormente. Além
disso, você precisa declarar explicitamente o repositório PrimeFaces para poder obter esses temas. Para poder armazenar as entidades JPA na memória H2,
configurei o persistence.xml no src/main/resources/META-INF da seguinte forma:

<?xml version="1.0" encoding="UTF-8"?>


<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="prod" transaction-type="JTA">
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>

A aparência da web.xml é a seguinte:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">

<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>bootstrap</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>*.xhtml</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>customers.xhtml</welcome-file>
</welcome-file-list>
</web-app>

Aqui estou especificando o tema de bootstrap PrimeFaces e o mapeamento de servlet para as visualizações. Neste exemplo, escreverei apenas uma visualização
( customers.xhtml) que deve ser carregada como arquivo central de boas-vindas.

Escrevendo a aplicação CRUD com JSF


A entidade central do Cliente JPA é modelada da seguinte forma:

@Entity
public class Customer {

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

@Column(nullable = false)
@NotEmpty
private String firstName;

@Column(nullable = false)
@NotEmpty
private String lastName;

@Column(nullable = false)
@NotEmpty
@Email
private String email;

@Past
private LocalDate dayOfBirth;

// constructors and getters & setters


}

Neste exemplo, estou misturando anotações JPA e anotações de validação de bean , o que é bom para esta pequena vitrine, mas para projetos maiores, eu
sugeriria usar algum tipo de objeto de transferência que obtivesse as anotações de validação de bean e fosse mapeado para a entidade JPA dentro um
serviço. As anotações de validação do bean ( @NotEmpty, @Email, @Past) são muito importantes porque nos ajudarão posteriormente na validação da
entrada do usuário.
Essas anotações declarativas não precisam de implementação de nossa parte, pois o servidor de aplicativos fornece uma implementação de referência em tempo
de execução. Outro recurso interessante que vem com a validação de bean e o componente de entrada PrimeFaces é que todos os campos de
entrada obrigatórios@NotEmpty (por exemplo , ou @NotNull) são marcados com um asterisco preto '*' fora da caixa.
Para a interação com o EntityManager , criei um EJB simples sem estado que é responsável pelas operações CRUD (criar, ler, atualizar e excluir). O código
deve ser bastante simples e não quero entrar em detalhes aqui:
@Stateless
public class CustomerManager {

@PersistenceContext
private EntityManager entityManager;

public List<Customer> loadAllCustomers() {


return this.entityManager.createQuery("SELECT c FROM Customer c", Customer.class).getResultList();
}

public void delete(Customer customer) {


if (entityManager.contains(customer)) {
entityManager.remove(customer);
} else {
Customer managedCustomer = entityManager.find(Customer.class, customer.getId());

if (managedCustomer != null) {
entityManager.remove(managedCustomer);

}
}
}

public void addNewCustomer(Customer customer) {

Customer newCustomer = new Customer();


newCustomer.setDayOfBirth(customer.getDayOfBirth());
newCustomer.setEmail(customer.getEmail());
newCustomer.setFirstName(customer.getFirstName());
newCustomer.setLastName(customer.getLastName());
newCustomer.setCustomerId(UUID.randomUUID().toString().substring(0, 8));

this.entityManager.persist(newCustomer);
}

public void update(List<Customer> customers) {

customers.forEach(entityManager::merge);
}
}

O bean de apoio correspondente para a visualização JSF também é bastante simples e mais ou menos apenas delega as operações ao EJB injetado.

@Named
@ViewScoped
public class CustomersBacking implements Serializable {

private List<Customer> customers;

private Customer customer = new Customer();

@Inject
private CustomerManager customerManager;

@PostConstruct
public void init() {
this.customers = customerManager.loadAllCustomers();
}
public void delete(Customer customer) {
customerManager.delete(customer);
customers.remove(customer);
}

public void add() {


customerManager.addNewCustomer(customer);
this.customers = customerManager.loadAllCustomers();
this.customer = new Customer();
}

public void update() {

customerManager.update(customers);
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Update successful"));
}

// getters & setters


}

Todos os clientes disponíveis são armazenados em uma lista dentro de @PostConstruct. O campo adicional cliente é necessário para armazenar as
entradas atuais do usuário final no objeto Cliente. Quando um novo cliente é armazenado no banco de dados no método add() , a referência será substituída
para obter campos de entrada vazios na visualização.
O método update() é responsável por atualizar todos os clientes disponíveis, pois não forneço um mecanismo de atualização para cada objeto de cliente. Isso é
tudo no back-end para nosso caso de uso simples. Vamos continuar com a visão. Vou dividir a explicação da view em duas partes, uma para a tabela de dados e
outra para o formulário de criação de um novo cliente.

Crie as visualizações JSF


A tabela de dados é agrupada em um formulário HTML simples para poder disparar alguma ação dentro de um elemento <p:commandButton> .

<h:form id="customers">
<p:growl id="growl" sticky="true" />

<p:dataTable id="customerList" var="customer" value="#{customersBacking.customers}">

<p:column headerText="Id">
<h:outputText value="#{customer.id}"/>
</p:column>

<p:column headerText="First name">


<p:inputText id="firstNameInput" value="#{customer.firstName}"/>
</p:column>

<p:column headerText="Last name">


<p:inputText id="lastNameInput" value="#{customer.lastName}"/>
</p:column>
<p:column headerText="Email">
<p:inputText id="emailInput" value="#{customer.email}"/>
</p:column>

<p:column headerText="Day of birth">


<h:outputText value="#{customer.dayOfBirth}">
<f:convertDateTime type="localDate" pattern="dd.MM.yyyy"/>
</h:outputText>
</p:column>

<p:column headerText="Customer ID">


<h:outputText value="#{customer.customerId}"/>
</p:column>

<p:column>
<p:commandButton update="customerList" value="Delete" icon="ui-icon-closethick"
action="#{customersBacking.delete(customer)}"
styleClass="ui-priority-primary">
</p:commandButton>
</p:column>

</p:dataTable>
<br/>
<p:commandButton style="float: right" id="save" value="Save" tyleClass="ui-priority-primary"
action="#{customersBacking.update}" icon="ui-icon-disk" update="growl">
<f:ajax execute="@form" render="@form"/>
</p:commandButton>
</h:form>

O namespace <p:…> está referenciando os componentes PrimeFaces, que são declarados na parte superior do arquivo .xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui">

No topo do <p:dataTable> estou declarando um elemento growl que é o componente PrimeFaces para exibir mensagens que são publicadas
no FacesContext atual . A tabela de dados está usando a lista de clientes do backing bean como sua fonte de dados e exibirá uma linha para cada cliente na
lista.
Além de apenas exibir os dados, adicionei elementos <p:inputText> para atributos que devem ser atualizáveis como nome, sobrenome e e-mail do cliente. Os
outros campos são somente leitura. Para a conversão do dia de nascimento do cliente, estou utilizando o elemento auxiliar <f:convertDateTime> .
Cada linha da tabela também recebe uma coluna extra com um botão de comando de exclusão para excluir a linha selecionada. O cliente que deve ser excluído
é passado para o método delete(Customer customer) do backing bean . Por padrão, os botões de comando PrimeFaces executam uma solicitação AJAX e
você pode especificar o elemento que deve ser atualizado após a chamada AJAX no updateatributo do elemento <p:commandButton> .
O último botão de comando é responsável por salvar as alterações feitas em diversos clientes e irá atualizar o elemento rosnado para a nova Mensagem do
Faces. A parte HTML da criação de um novo cliente também é bastante simples:

<h:form id="add">
<h:panelGrid columns="3" cellpadding="5" style="margin: 0 auto;">
<p:outputLabel for="firstName" value="First name" />
<p:inputText id="firstName" value="#{customersBacking.customer.firstName}" />

<p:message for="firstName" />


<p:outputLabel for="lastName" value="Last name" />
<p:inputText id="lastName" value="#{customersBacking.customer.lastName}">
</p:inputText>

<p:message for="lastName" />

<p:outputLabel for="email" value="E-Mail" />


<p:inputText id="email" value="#{customersBacking.customer.email}">
</p:inputText>

<p:message for="email" />

<p:outputLabel for="dayOfBirth" value="Day of birth" />


<p:calendar id="dayOfBirth" value="#{customersBacking.customer.dayOfBirth}" pattern="dd.MM.yyyy" mask="true">
<f:convertDateTime type="localDate" pattern="dd.MM.yyyy" />
</p:calendar>

<p:message for="dayOfBirth" />

<p:commandButton update="@form :customers:customerList" value="Add" action="#{customersBacking.add}">


</p:commandButton>

</h:panelGrid>
</h:form>

Para cada atributo modificável, estou fornecendo um componente outputLabel , inputText e message . Para o dia de nascimento, adicionei um componente
simples de calendário PrimeFaces que também fornecerá uma máscara para o formato de data necessário. Quando o usuário insere dados e tenta criar um
novo usuário, os dados de entrada serão validados de acordo com as anotações de Validação do Bean na entidade Cliente. Um nome ausente, um e-mail
formatado incorretamente ou um dia de nascimento futuro serão detectados e uma mensagem de erro será exibida:
Considerando o caso de uso e o público-alvo, acho que essa abordagem é uma forma realmente produtiva de criar aplicações web. Com os
componentes PrimeFaces , você obtém um conjunto de elementos prontos para uso e com o tema correto, eles também ficarão muito bonitos.
Escrever este aplicativo com um SPA e um backend de sua escolha levaria muito mais tempo e você produziria mais código. Experimente este exemplo você
mesmo ou dê uma olhada em meu repositório GitHub para obter a base de código completa e um tutorial passo a passo para executá-lo em sua máquina
no README.md do projeto.
Há muito mais para descobrir com JSF 2.3 e Java EE em geral (por exemplo, WebSockets , Internacionalização , Segurança …), então fique atento aos
próximos posts do blog. Para começar com JSF 2.3 , posso recomendar o Guia Definitivo para JSF em Java EE 8 de Bauke Scholtz ( @OmniFaces ) e Arjan
Tijms ( @arjan_tijms ).

Você também pode gostar