15 de fev. de 2016

Um CRUD com JavaFX

Finalmente!!! Uma das postagens mais esperadas desse blog é o CRUD! 

VEJA A PARTE 2 COM BANCO DE DADOS

Todo programador já fez(ou vai fazer) um CRUD na vida e é, no geral, a forma que aprendemos conceitos básicos de uma tecnologia. Nesse artigo vamos mostrar um simples CRUD em JavaFX para explorar algumas características da tecnologia.

Definição de CRUD


CRUD é uma sigla que vem das operações básicas que podemos fazer um algo armazenado em uma fonte de dados:

Create: Criar uma nova instância de algo na fonte dos dados;
Retrieve: Trazer os dados já armazenados;
Update: Atualizar dados já armazenados;
Delete: Apagar.

Onde armazenar os dados?


A fonte de dados pode ser um banco de dados(MySQL, MariaDB, Postgres, etc), um arquivo(arquivos em diversos formatos, como CSV), um Web Service (que após chamado, salva os dados em algum lugar) ou até mesmo a própria memória dinâmica do computador(sendo que os dados se perdem quando a aplicação é fechada). Aqui vamos usar um arquivo CSV, uma forma simples de armazenar objetos em um arquivo de texto.
O motivo de não usarmos um banco de dados tradicional é que o foco do artigo é mostrar JavaFX. Um banco de dados comum implica em termos que falar de SQL, conexão com o banco, select, etc, isso vai fugir do foco.

Nosso CRUD


Nossa aplicação simples vai ser um CRUD de Contas. Sim, bills, contas! Pagamos todo mês contas de luz, água, gás, faturas, etc, etc, argh. Para representar a conta, criamos um objeto Java chamado Conta com três atributos: id (gerado automaticamente para controle interno), concessionária, descrição e data de vencimento. É isso, simples, não? Em breve mostramos o código dessa classe, mas agoras que conhecemos os campos, veja a telinha que modelamos usando o SceneBuilder que gerou um FXML (se não sabe o que é isso, veja esse artigo sobre FXML)




Começamos com uma tabela com três colunas representando os campos, após a tabela temos os campos de texto para entrada do nome, descrição e um campo para entrada de data do tipo DatePicker, que infelizmente ainda não abordamos aqui por ter sido adicionado apenas no JavaFX 8, e por fim os botões de ações.

A lógica da aplicação é a seguinte:
  • A tabela tem ID tblContas e três colunas: clConc, clDesc e clVenc. Elas são populadas com os dados de um objeto do tipo Conta;
  • Os campos de texto e o campo de data tem um (txtConc, txtDesc, dpVenc) serão injetados no controller para que possamos saber o valor que o usuário entrou;
  • Cada um dos botões  ação:
    • salvar: salva o objeto de acordo com a informação entrada pelo usuário. Não está habilitado quando um campo está selecionado na tabela;
    • atualizar: Só está habilitado quando selecionamos uma linha da coluna e permite atualizar os dados dessa linha (os campos de entrada de dados vão ser atualizados com o valor selecionado para serem modificados pelo usuário);
    • apagar: apaga a linha selecionada;
    • limpar: limpa o campo selecionado atualmente.
As operações e os elementos da tela ficam na classe ContasController. Código que veremos já já.

Fazendo as operações com o banco de dados

As operações em sí com o banco ficam na interface ContasService. Ela contém os métodos salvar, que recebe uma instância de conta a ser salva, atualizar, que recebe a conta já salva para ser atualizada, apagar, que apaga uma conta e buscarTodos, que retorna todas as contas selecionadas. É nessa classe que fazemos as operações. 
Todo o código poderia ficar dentro do controller, MAS ISSO É COISA FEIA, temos que definir os métodos em uma interface e, vejam que interessante, usando a capacidade do Java 8 de definir métodos padrões, criamos um método getInstance para retornar a implementação que queremos dessa interface. Assim, criamos a clase ContasCSVService, que é uma implementação da interface, e retornamos uma nova instância nesse método! Podemos, obviamente, criar uma interface, por exemplo ContasBDService que faria a mesma coisa, mas que invés de usar um arquivo CSV, se comunica com um banco de dados, a mesma ideia poderia ser aplicada para um arquivo XLS, como ContasXLSService, e por aí vai. O código do controller, que os métodos da interface, não iria sofrer nenhuma modificação, pois só precisaríamos trocar a intância de ContasService retornada no método getNewInstance! (claro que com CDI e outras tecnologias, sequer criar manualmente precisaríamos). Veja a nossa interface



VAMOS AO CÓDIGO!


Agora, depois desse mooonnnntteee de papo, vamos ao código. Claro que se você leu acima com atenção, vai ser muito simples de entender tudo, mas mesmo assim o código está comentado na medida do possível.

Não vou colocar o todo código na postagem, pois já está muito extensa, mas veja o código da classe Conta, da interface ContasService e da classe ContasController. O restante está no github!



17 comentários:

  1. Muito legal, com lambda economiza muito código e fica ais limpo !

    ResponderExcluir
  2. William, estou com uma duvida nesse método BooleanBinding e também nessa linha:

    tblContas.getSelectionModel().selectedItemProperty().addListener((b, o, n) -> {

    O que faz isso? Esse n.getConcessionaria() o que faz também? n.get();

    ResponderExcluir
    Respostas
    1. Olá,

      O add listener adiciona um ouvinte ao item selecionado na tabela, quando a tabela muda o item selecionado, aquele código é invocado.

      Para entender binding, por favor, veja nossa postagem sobre isso:

      http://aprendendo-javafx.blogspot.com.br/2014/08/api-do-javafx-properties-listeners-e.html

      Excluir
  3. bostacard kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk

    ResponderExcluir
  4. Como faz para formatar a data da tabela para 22/04/2016?

    ResponderExcluir
  5. O meu está dando error, Location is required, o que pode ser?

    ResponderExcluir
  6. Parabéns! Esse blog me ajuda demais!!!
    Muitíssimo obrigado!

    ResponderExcluir
  7. Boa tarde. Tenho interesse em aprender a utilizar JaxaFX em meus novos projeto e resolvi baixar este para tentar entender melhor. Mas ao tentar rodar no NetBeans ocorre a seguinte esceção
    Caused by: java.lang.NullPointerException: Location is required.
    (...)
    at org.aprendendojavafx.crud.view.App.start(App.java:27)
    O trecho de código em questão é:
    "linha 26 URL fxml = getClass().getResource("./contas.fxml");
    linha 27 Parent parent = FXMLLoader.load(fxml);"
    Sei que é algo relacionado a referência do arquivo "contas.fxml" e já tentei algumas soluções que busquei em outros fóruns mas sem sucesso. Poderia me auxiliar, por favor?
    OBS: A estrutura dos arquivos no projeto está tal qual baixei do github.

    ResponderExcluir
    Respostas
    1. Se estiver no Windows, retira o "./", ficando apenas "contas.fxml"

      Excluir
  8. Olá a todo eu sou o Nicolau e tenho o interesse de aprender javafx a partir da introdução ate a conclusão. Uma aula básica de como funciona, apenas para mim ter uma noção.

    ResponderExcluir
  9. Olá, William! Quando tento salvar está apresentando os erros abaixo: Uso windows 64 bits, eclipse e scene builder.

    java.nio.file.AccessDeniedException: Dados.csv
    at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
    at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(Unknown Source)
    at java.nio.file.spi.FileSystemProvider.newOutputStream(Unknown Source)
    at java.nio.file.Files.newOutputStream(Unknown Source)
    at java.nio.file.Files.write(Unknown Source)
    at jv.alavec.javafx.crudservice.ContasCSVService.salvaDados(ContasCSVService.java:95)
    at jv.alavec.javafx.crudservice.ContasCSVService.salvar(ContasCSVService.java:78)
    at jv.alavec.javafx.crudcontroller.Controle.salvar(Controle.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
    at

    ResponderExcluir
    Respostas
    1. Bom dia, sei que o post é antigo.
      Mas conseguiu resolver esse problema?

      Excluir
  10. Boa noite, mas se no caso ai você tivesse que colocar uma coluna de Buttons como por exemplo um button de apagar o dado, como você faria a implementação disso nas colunas da TableView ?

    ResponderExcluir
  11. e "javafx.scene.control.TextField.textProperty()" because "this.txtNomeFilme" is nul

    ResponderExcluir