Wednesday, October 18, 2017

DataSource no servidor WildFly utilizando JPA, JSF, Maven e Eclipse: parte 1

Aplicações Java Enterprise acessam banco de dados utilizando a API JDBC. Nela os bancos de dados são acessados usando objetos do tipo DataSource, os quais possuem uma série de propriedades que identificam o banco de dados real como a URL do servidor, o nome do banco de dados, senha, etc.

Antes de acessar o banco de dados, a aplicação precisa criar uma conexão, e um objeto DataSource funciona como um factory de conexões entre o banco e a aplicação. Quando o DataSource é gerenciado pelo servidor de aplicações, geralmente a ele é associado um JNDI, que é um nome para o datasource. Assim quando a aplicação demanda uma conexão, ela utiliza o JNDI, o servidor instancia o(s) objeto(s) DataSource e disponibilizam a conexão.

Datasources em servidores Java EE geralmente implementam o poolingque são algoritmos de instanciação de DataSources totalmente transparentes à aplicação os quais otimizam a liberação, abertura e fechamento de conexões com o banco de dados.

DataSources para o banco de dados MySQL no servidor WildFly 10

Faça download da API JDBC do MySQL (mysql-connector-java-5.1.21.jar) e, caso necessário, baixe também o servidor WildFly 10 Java EE7 Full & Web Distribution . No MySQL, crie um banco de dados chamado EmpresaBD.

No WildFly, DataSources também são chamados de módulos. Assim, para acessar o MySQL do servidor precisamos instalar um novo módulo para ele. No diretório raiz do WildFly, há a pasta modules. A partir dela crie os diretórios /com/mysql/main/ da forma que segue:

WILDFLY_HOME/modules/com/mysql/main/

Dentro da pasta main cole o driver do MySQL que você acabou de baixar e crie um arquivo chamado module.xml, o qual conterá a definição do módulo cujo nome é com.mysql. 

A variável JBOSS_HOME aponta para pasta module e os arquivos e pastas criados lá dentro serão usados para carregar o datasource quando o servidor iniciar. O conteúdo do arquivo module.xml deve ser como segue:
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="com.mysql"> 
    <resources>
        <resource-root path="mysql-connector-java-5.1.21.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

Salve e feche o arquivo module.xml. A estrutura de diretórios que criamos então fica dessa forma:
  • WILDFLY_HOME/modules/com/mysql/main/mysql-connector-java-5.1.21.jar
  • WILDFLY_HOME/modules/com/mysql/main/module.xml

O módulo que permite acessar o MySQL do WildFly está instalado. Agora só precisamos criar o data source para o banco EmpresaDB. Para isso temos que editar o arquivo standalone.xml passando as coordenadas do banco (usuário, senha, etc.). Esse arquivo está localizado em:
  • WILDFLY_HOME/standalone/configuration/standalone.xml 
Abra esse arquivo, localize a tag <dataSources> e dentro dela cole o seguinte trecho (ajustando usuário e senha de acordo com suas configurações pessoais):
<datasource jta="true" jndi-name="java:jboss/datasources/empresaDB_DS_mysql"
 pool-name="empresaDB_DS_mysql" enabled="true">

 <connection-url>jdbc:mysql://localhost:3306/EmpresaDB</connection-url>
 <driver-class>com.mysql.jdbc.Driver</driver-class>
 <driver>mysql</driver>
 <security>
  <user-name>root</user-name>
  <password>P@ssword1</password>
 </security>

</datasource>

Ainda dentro da tag <dataSources> localize a tag <drivers> e dentro dela cole este trecho:
<driver name="mysql" module="com.mysql"/>
Repare que a tag driver aponta para o módulo que criamos. E o datasource que criamos aponta para a esta tag driver cujo nome é mysql.

Salve e feche o arquivo standalone.xml. Pronto. Inicie o servidor WildFly executando o arquivo standalone.sh (para Linux) ou standalone.bat (para Windows) localizado em WILDFLY_HOME/bin/

Acesse o console do servidor na porta 9990. Clique na aba Configurations, depois vá clicando em Subsystems, DataSources, Non-XA. Localize o datasource criado EmpresaDB_DS_mysql e teste a conexão. Conforme a imagem:



Repare que o nome que registramos, empresaDB_DS_mysql, é o que será usado na aplicação para que ela solicite ao servidor uma conexão com o banco de dados EmpresaDB.

Na parte 2 deste artigo criamos uma aplicação JavaEE que acessa o DataSource.

Referências:
ĆMIL , Michal; MATLOKA, Michal ; MARCHIONI, Francesco . Java EE 7 Development with WildFly. 2. ed. Birmingham: Packt Publishing Ltd., 2013

Access restriction: The type 'Application' is not API (restriction on required library '/usr/lib/jvm/jdk1.8.0_144/jre/lib/ext/jfxrt.jar')

Recentemente ao criar um projeto JavaFX no Eclipse, a IDE disparava uma advertência sempre que eu tentava importar algo do pacote javafx.*


Para corrigir isso, vá em propriedades do projeto. Na janela que abrir selecione Java Build Path, vá na tab libraries e expanda o item JRE System Library, conforme a imagem:


Selecione o item Access Rule e depois o botão Edit.... Na janela que abrir clique no botão Add... .Na caixa de texto Rule Pattern coloque javafx/** e na combo box Resolution escolha a opção Accessible Conforme a imagem:


Clique em OK, Apply e OK. Limpe e construa seu projeto novamente. As advertências deixam de aparecer.



Monday, October 16, 2017

Generic Lazy Loading com JSF e PrimeFaces

Lazy Loading é um padrão de projeto que atrasa o carregamento de dados na memória até o momento quando eles são estritamente necessários. Em sistemas orientados a objeto, as entidades estão sempre relacionadas umas com as outras de modo que esses níveis de relacionamento podem se extender indefinidamente dependendo do tamanho do sistema. Se a aplicação não implementa uma estratégia adequada para listar os registros em uma tabela, ela corre o risco de carregar uma quantidade absurda de dados na mémória sem necessidade, o que pode prejudicar seu desempenho.

O componente p:dataTable do framework PrimeFaces permite implementar LazyLoading de forma muito simples bastando que o desenvolvedor estenda LazyDataModel<T>, onde T é o tipo da entidade que será carregada na tabela. Por exemplo, suponha que você tenha uma entidade Pessoa, você deveria extender LazyDataModel da seguinte forma:
public class LazyTablePessoa extends LazyDataModel<Pessoa>{

   private PessoaService service;

   @Override
   public List<Pessoa> load(int first, int pageSize, String sortField, 
                               SortOrder sortOrder, Map<String, Object> filters) {

     
        List<Pessoa>listPessoa = pessoaService.getPessoas(first, first + pageSize);
        int linhas = pessoasService.countPessoas();
        setRowCount(linhas);
        return listPessoa;
    }
  //... outros métodos
}
Essa abordagem possui um problema se o projeto for crescendo e um número cada vez maior de entidades demandar lazy loading em determinadas telas. Dessa forma seria necessário implementar  LazyDataModel para cada entidade que você queira exibir. Se houver 100 entidades para listar, você terá que implementar 100 extensões de LazyDataModel, cada uma com o código praticamente idêntico!

Generic Lazy Data Table
Somente os dados exibidos são carregados na memória.

Podemos reduzir drasticamente essa quantidade de implementações criando uma única extensão genérica de LazyDataModel que atenda, digamos, 90% de todas as necessidades de exibição, ou seja, você podera ter 100 entidades no seu modelo, mas uma única implementação de LazyDataModel será suficiente para listar as entidade de acordo com o padrão lazing.

Para implementar este padrão precisamos criar:
  1. Uma DataTable genérica que extenda LazyDataModel
  2. Uma interface de serviço genérica que busque os dados. As especificações de busca para cada entidade variam de acordo com a implementação.
O diagrama de classes abaixo resume o modelo:


Implementando o diagrama

Pessoa.java
public class Pessoa {
    
    private String nome;
    private int idade;
    private Date nascimento;

    //métodos getters & setters
}

GenericService.java
public interface GenericService<T> {
    //a quantidade de registros que serão carregados
    List<T> buscaPaginada(int inicio, int fim);
    //a quantidade de registros na fonte de dados
    int countLinhas();
}
PessoaService
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PessoaService implements GenericService<Pessoa>{

    //representando o banco de dados
    private List<Pessoa> dataSource;
    
    public PessoaService() {

        dataSource = new ArrayList<>();
        for(int i = 0; i < 100; i++){
            Pessoa p = new Pessoa();
            p.setNome("Pessoa "+i);
            p.setIdade(i);
            p.setNascimento(new Date());
            dataSource.add(p);
        }        
    }    

    //implementação para a entidade pessoa
    //as regras podem variar de entidade para entidade...
    @Override
    public List<Pessoa> buscaPaginada(int inicio, int fim) {        

        return dataSource.subList(inicio, fim);
    }

    @Override
    public int countLinhas() {

        return dataSource.size();
    }    
}

Nossa implementação de GenericLazyDataTable. Repare que o tipo do objeto é genérico (T), ou seja, a princípio não se sabe qual é o tipo de objeto sendo buscado nem qual é o critério de busca, que
dependerá da implementação fornecida para a interface GenericService.

import java.util.List;
import java.util.Map;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;

public class GenericLazyDataTable<T> extends LazyDataModel<T>{
        
    private final GenericService genericService;

    public GenericLazyDataTable(GenericService genericService) {

        this.genericService = genericService;
    }

    @Override
    public List<T> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {

        int linhas = genericService.countLinhas();
        this.setRowCount(linhas);
        return genericService.buscaPaginada(first, first + pageSize);
    }    
}

O managed bean controller da página JSF
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class ManagedBean implements Serializable{
    
    private GenericLazyDataTable genericLazyDataTable;
    private GenericService genericService;    

    @PostConstruct
    public void init(){        

        genericService = new PessoaService();
        genericLazyDataTable = new GenericLazyDataTable(genericService);
    }

    public GenericLazyDataTable getGenericLazyDataTable() {

        return genericLazyDataTable;
    }        
}

E a página inicial index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!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:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Lazy Pessoa</title>
    </h:head>

    <h:body>
        <p:dataTable value="#{managedBean.genericLazyDataTable}" var="pessoa" lazy="true" rows="20" paginator="true">

            <p:column headerText="nome">
                <h:outputText value="#{pessoa.nome}" />
            </p:column>

            <p:column headerText="idade">
                <h:outputText value="#{pessoa.idade}" />
            </p:column>

            <p:column headerText="nascimento">
                <h:outputText value="#{pessoa.nascimento}" >
                    <f:convertDateTime pattern="dd/MM/yyyy" locale="pt" />
                </h:outputText>
            </p:column>

        </p:dataTable>
    </h:body>
</html>

Estrutura do projeto na IDE NetBeans 8.1 (utilizando Maven)


Tornando a implementação ainda mais genérica

É possível tornar esse modelo ainda mais genérico. Por exemplo poderíamos criar um campo Map em GenericLazyDataTable e sobrecarregar o método buscaPaginada de GenericService para definir filtros de busca. Algo como: 
private Map<String, Object> filtrosPersonalizados;

//outros campos...

//pode ser chamado pelo Managed Bean
public void adicionarFiltro(String nomeDocampo, Object tipoDoCampo) {

        filtrosPersonalizados.put(campo, object);
}