Neste artigo veremos como trabalhar com o componente JTable do Java, um dos mais completos e complexos do pacote Swing. Veremos o seu uso através de um exemplo completo, desde a definição dos Beans até a criação dos formulários.

Desenvolvimento do Projeto

Para nosso projeto utilizaremos o banco de dados nativo do Java, o Apache Derby. Começaremos configurando-o e criando um banco de dados para uso no nosso projeto. Para este artigo estamos utilizando a IDE Netbeans 7.2, mas fique à vontade para utilizar outra versão de sua preferência.

Com o NetBeans aberto, vá até a aba Serviços e depois clique duas vezes em “Banco de Dados”. Você verá a opção “Java DB”, que refere-se ao Apache Derby. Então clique com o botão direito do mouse e vá em “Criar banco de dados”. Logo em seguida aparecerá uma tela para que você possa preencher as propriedades do seu banco de dados; configuraremos da seguinte forma:

  1. Nome do Banco: dbusuario
  2. Nome do Usuário: root
  3. Senha: 1234

Feito isso, veremos a criação de um novo link jdbc:derby://localhost:1527/dbusuario. Clique com o botão direito neste link e vá em “Conectar”. Depois de conectado devemos clicar com o botão direito novamente e ir em Executar comando, assim, seremos redirecionados para uma tela onde poderemos digitar os comandos em SQL para criação da nossa tabela usuario, conforme a Listagem 1.


CREATE TABLE usuario(
 login varchar(50) not null primary key,
 senha varchar(50) not null,
 nome varchar(255) not null
);
Listagem 1. SQL para criação da tabela usuário

Nossa tabela é bem simples e possui apenas três campos: login, senha e nome. Depois de colocar o comando da Listagem 1 na linha de comando do Derby basta executá-lo e já temos nossa estrutura do banco de dados pronta. Podemos agora partir para a codificação em Java.

Em Java precisamos garantir que a conexão com o banco de dados seja estabelecida, para isso criaremos uma classe chamada Conexao.java, conforme a Listagem 2, que terá por responsabilidade manter e retornar a conexão do banco de dados em aberta ou abrir uma caso não exista.


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.crudusuario.security;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
public class Conexao {
    
    private static Connection connection;
    
    public static Connection getConnection() throws ClassNotFoundException, SQLException{
        if (connection == null){
          Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
          String url = "jdbc:derby://localhost:1527/dbusuario";
          connection = DriverManager.getConnection(url, "root", "1234");
        }
        
        return connection;    }   
      }
Listagem 2. Classe Conexao

A classe Conexao possui um atributo estático connection, que armazenará a conexão atual, ou seja, se ele for nulo, uma nova conexão será criada, caso contrário será apenas retornada a instância da conexão atual, evitando que sejam criadas diversas conexões. O nosso Class.forName() carrega o driver do Apache Derby para que possamos utilizá-lo, e o DriverManager.getConnection() retorna uma instância de Connection dado os parâmetros de conexão necessários.

Feito isto já temos a nossa conexão com o banco estabelecida a qualquer momento que precisarmos, basta chamado o método: Conexao.getConnection() e estaremos conectados.

Criamos o nosso banco e depois definimos uma forma de conexão via Java, agora iremos definir nossa regra de negócio necessária para a aplicação em questão. Nossa aplicação é muito simples e tem como objetivo listar os usuários cadastrados usando um JTable. Para que isso seja possível, o mais aconselhável é que nossa estrutura esteja pronta para suportar operações com o banco de dados, então começaremos definindo o nosso Bean, ou seja, a forma que temos de mapear a nossa tabela usuario do banco de dados para o Java. Veja a Listagem 3.


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.crudusuario.bean;
 
/**
 *
 * @author LAB4-PC03
 */
public class Usuario {
    
    private String login;
    private String senha;
    private String nome;
 
    public String getLogin() {
        return login;
    }
 
    public void setLogin(String login) {
        this.login = login;
    }
 
    public String getSenha() {
        return senha;
    }
 
    public void setSenha(String senha) {
        this.senha = senha;
    }
 
    public String getNome() {
        return nome;
    }
 
    public void setNome(String nome) {
        this.nome = nome;
    }   
    
    
}
Listagem 3. Bean Usuario.java

Os métodos e atributos do bean são definidos de acordo com a nossa tabela usuario, já que ele é apenas um “espelho” da mesma.

Precisamos agora criar alguma classe que faça a comunicação do nosso ambiente gráfico de interação com o usuário (formulários) com o banco de dados. Por exemplo, quando o usuário clicar no botão “listar”, precisaremos de uma classe que vá até o banco e busque todos os usuários cadastrados para que estes possam ser mostrados no nosso formulário.

Poderíamos colocar essa lógica de comunicação com o banco de dados dentro do nosso formulário, mas esta não é uma boa prática, visto que se desejarmos realizar a mesma busca em outro formulário estaríamos repetindo código, então o melhor é centralizar essas operações em uma única classe, como mostra a Listagem 4.


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.crudusuario.dao;
 
import br.com.crudusuario.bean.Usuario;
import br.com.crudusuario.security.Conexao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
public abstract class UsuarioDAO {
 
    public static Usuario getUsuarioByLogin(String login) throws ClassNotFoundException, SQLException {
        Connection con = Conexao.getConnection();
        PreparedStatement ps = con.prepareStatement("SELECT * FROM usuario WHERE login = ?",
                ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        ps.setString(1, login);
        return populaUsuario(ps.executeQuery());
    }
 
    public static void deleteByLogin(String login) throws SQLException, ClassNotFoundException {
        Connection con = Conexao.getConnection();
        PreparedStatement ps = con.prepareStatement("DELETE FROM usuario WHERE login = ?");
        ps.setString(1, login);
        ps.execute();
    }  
 
    public static List<Usuario> getUsuarios() throws ClassNotFoundException, SQLException {
        Connection con = Conexao.getConnection();
        PreparedStatement ps = con.prepareStatement("SELECT * FROM usuario");
        ResultSet rs = ps.executeQuery();
        List<Usuario> usuarios = new ArrayList<Usuario>();
        while (rs.next()) {
            Usuario usuario = new Usuario();
            usuario.setLogin(rs.getString("login"));
            usuario.setNome(rs.getString("nome"));
            usuario.setSenha(rs.getString("senha"));
            
            usuarios.add(usuario);
        }
 
        return usuarios;
    }
 
    private static Usuario populaUsuario(ResultSet rs) throws SQLException {
        rs.first();
        Usuario usuario = new Usuario();
        usuario.setLogin(rs.getString("login"));
        usuario.setNome(rs.getString("nome"));
        usuario.setSenha(rs.getString("senha"));
 
        return usuario;
    }
 
}
Listagem 4. Classe UsuarioDAO

Vamos entender melhor cada um dos nossos métodos apresentados:

  1. getUsuarioByLogin() : Dado um login qualquer, este método retorna uma instância de Usuario. Perceba que já fazemos o uso do Conexao.getConnection() e, logo em seguida, usamos um outro método chamado populaUsuario();
  2. deleteByLogin(): Deleta um determinado usuário de acordo com o login passado.
  3. getUsuarios(): É um dos nossos métodos principais, pois é quem irá popular nossa JTable. Este retorna uma lista de Usuários.
  4. populaUsuario(): Perceba que em quase todos os métodos fizemos o uso deste, pois este método converte o ResultSet para o Bean, que pode ser manipulado pela lógica do nosso sistema, assim não precisamos ficar trabalhando com ResultSet e sim direto com o Bean.

No NetBeans, clique com o botão direito em cima do seu projeto e vá em Novo -> Form JFrame…. Será criado um formulário onde devemos inserir o componente JTable e um JButton, como podemos ver na Figura 1.

Formulário para listagem de usuários
Figura 1. Formulário para listagem de usuários

Precisamos agora definir os métodos necessários para inicializar nosso JTable corretamente. Este possui uma “ligação” com o TableModel, onde é possível realizarmos o mapeamento do nosso bean Usuario com a listagem, assim temos uma listagem dinâmica baseada no banco de dados. Observe a Listagem 5.


public FListaUsuario() throws ClassNotFoundException, SQLException {        
    initComponents();
     tabelaUsuarios = (DefaultTableModel) jTableUsuarios.getModel();
    carregarUsuariosNoTable();
}


private void carregarUsuariosNoTable() throws ClassNotFoundException, SQLException{
   tabelaUsuarios.setNumRows(0);
    for (Usuario u : UsuarioDAO.getUsuarios()) {
        tabelaUsuarios.addRow(new Object[] { u.getNome(), u.getSenha(),u.getLogin()
        });
    }
}
Listagem 5. Inicializando JTable

Temos dois métodos importantes: o construtor do nosso formulário e um método responsável por carregar os usuários no nosso JTable. Logo que abrimos nosso formulário pela primeira vez o construtor é chamado e neste momento usamos o método getModel(), que nos retorna um objeto que implementa TableModel. Então nesse caso podemos usar o DefaultTableModel, que é uma das classes que implementa a interface TableModel. De posse do objeto DefaultTableModel na variável tabelaUsuarios, passaremos a trabalhar com ele para realizar as operações relativas ao banco de dados, como por exemplo, carregar os usuários na tabela.

O nosso método carregarUsuariosNoTable() é quem faz a tarefa de carregar os usuários retornados pela classe UsuarioDAO no JTable. Isso é feito setando o nosso TableModel com rows = 0 através do método setNumRows(0), assim garantimos que não há nenhum registro para evitar duplicações. Depois disso capturamos a lista de usuários através do UsuarioDAO.getListaUsuarios() e iteramos nesta lista, adicionando objeto por objeto através do método addRow() do TableModel.

Vejamos agora nosso método de deleção presente na Listagem 6.


private void jButtonDeletarActionPerformed(java.awt.event.ActionEvent evt) {                                                  
  if (JOptionPane.showConfirmDialog(this, "Tem certeza que deseja deletar este usuário ?")
          == JOptionPane.YES_OPTION){
      String loginSelecionado = jTableUsuarios.getValueAt(jTableUsuarios.getSelectedRow(),
              2).toString();
        try {      
            UsuarioDAO.deleteByLogin(loginSelecionado);
            JOptionPane.showMessageDialog(this, "Usuário deletado com sucesso");
            carregarUsuariosNoTable();
        } catch (SQLException ex) {
            Logger.getLogger(FListaUsuario.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(FListaUsuario.class.getName()).log(Level.SEVERE, null, ex);
        }
  }               
}
Listagem 6. Deleção de usuários

Na deleção do usuário confirmamos se é isto mesmo que deve ser feito e logo depois capturamos o valor do login de acordo com o número da linha selecionado. Por exemplo, se ele está na linha 2, então para nós será a linha 1 já que a contagem começa do 0. Sendo assim passamos a linha 1 como parâmetro e a coluna 2 (0 = nome, 1 = senha e 2 = login) e o usuário será deletado.

Logo após a deleção do usuário precisamos atualizar a lista com os novos usuários. Temos duas formas de fazer isso:

  1. Removendo a linha do JTable;
  2. Recarregando os usuários no JTable.

Claro que o método 1 é mais rápido, porém, para treinarmos o uso do método de carregamento através do banco de dados optamos por utilizar o método 2.

No JTable você deve usar o TableModel que usamos anteriormente, pois através deste você consegue manipular a maioria das funcionalidades dispostas no JTable, como mostra a Listagem 7.


import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class JTableTest {
  
  public static void main(String[] argv) throws Exception {
    DefaultTableModel model = new DefaultTableModel();
    JTable table = new JTable(model);
    model.addColumn("Col1");
    model.addRow(new Object[] { "r1" });
    model.addRow(new Object[] { "r2" });
    model.addRow(new Object[] { "r3" });
  }
}
Listagem 7. Adicionando colunas e linhas

Temos um exemplo simples de criação de um JTable, onde definimos um DefaultTableModel que implementa a interface TableModel. Através do DefaultTableModel controlamos as propriedades de colunas e linhas do JTable. Veja que na Listagem 7 usamos o addColumn() e o addRow(). O addRow() aceita um array onde cada item corresponde a coluna que inserimos anteriormente, e como usamos apenas uma coluna, nosso array sempre terá apenas um item. Veja como ficaria se tivéssemos mais colunas na Listagem 8.


import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class JTableTest {
  
  public static void main(String[] argv) throws Exception {
    DefaultTableModel model = new DefaultTableModel();
    JTable table = new JTable(model);
    model.addColumn("Col1");
    model.addColumn("Col2");
    
    model.addRow(new Object[] { "r11","r12" });
    model.addRow(new Object[] { "r21","r22" });
  }
}
Listagem 8. Adicionando várias e colunas e várias linhas

Perceba que a quantidade de objetos no nosso array aumentou para a mesma quantidade de colunas, esse é o princípio da manipulação colunas e linhas usando o DefaultTableModel.

Outro método interessante é o clearSelection(), que é responsável por limpar a seleção de qualquer linha que esteja selecionada, juntamente com outro método setRowSelectionInterval() responsável por selecionar uma quantidade de linhas desejadas. Com estes dois métodos podemos trabalhar com a manipulação de seleções do JTable. Vejamos a Listagem 9.


import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class JTableTest {
  
          public static void main(String[] argv) throws Exception {
    DefaultTableModel model = new DefaultTableModel();
    JTable table = new JTable(model);
    model.addColumn("Col1");
    model.addColumn("Col2");
    
    model.addRow(new Object[] { "r11","r12", "r13" });
    model.addRow(new Object[] { "r21","r22", "r23" });
    
    table.setRowSelectionInterval(0, 1);
    table.clearSelection();
  }
}
Listagem 9. Trabalhando com seleção

Na linha setRowSelectionInterval(0,1) selecionamos as linhas 0 e 1 do nosso JTable e logo em seguida limpamos esta seleção através do clearSelecion(). Obviamente que estas linhas devem ser criadas previamente, por isso fizemos questão de repetir o código aonde adicionamos as colunas e as linhas.

É importante salientar que o método setRowSelecionInterval() trabalha com o conceito de que tanto a linha inicial e a linha final também serão selecionadas, e não apenas as que estão entre elas. Por exemplo, imagine que seu JTable possui 10 linhas (de 0 à 9), e você faz setRowSelecionInterval(4,8): isso irá selecionar as linhas: 4,5,6,7,8.

Além deste tipo de seleção mais específica, temos uma mais genérico e simples, selectAll() irá selecionar tudo, ou seja, linhas e colunas contidas no JTable.

Podemos também selecionar apenas as colunas com o método setColumnSelectionInterval(), onde devemos passar os índices das colunas iniciais e finais que gostaríamos de selecionar. Ele segue o mesmo princípio do setRowSelectionInterval, por isso não colocaremos listagem para tal.

Outro ponto importante da seleção é o getSelectedRow() e o getSelectedColumn(). Imagine que o usuário selecione alguma linha da tabela e clique no botão deletar (como fizemos anteriormente): o getSelectedRow() nos mostrará que linha foi selecionada, e por outro lado o getSelectedColumn() irá mostrar quando uma coluna for selecionada.

Vejamos agora o getColumnCount(), presente na Listagem 10, que nos ajudará a retornar a quantidade de colunas que nosso JTable possui.


import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class JTableTest {
  
          public static void main(String[] argv) throws Exception {
    DefaultTableModel model = new DefaultTableModel();
    JTable table = new JTable(model);
    model.addColumn("Col1");
    model.addColumn("Col2");
    model.addColumn("Col3");
    model.addColumn("Col4");
    model.addColumn("Col5");
 
    
    System.out.println(table.getColumnCount());
    
  }
} 
Listagem 10. Retornando a quantidade de colunas

Temos aqui que o retorno será igual a 5, isso porque criamos cinco colunas no nosso Model.

Por último, vejamos um método também muito interessante para permitir a edição de uma célula em especial: editCellAt(int row, int column). Com este método podemos habilitar a edição de uma determinada célula para o usuário alterar o seu valor, como mostra a Listagem 11.


import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class JTableTest {
  public static void main(String[] argv) throws Exception {
    DefaultTableModel model = new DefaultTableModel();
    JTable table = new JTable(model);
    model.addColumn("Col1");
    model.addColumn("Col2");
    model.addColumn("Col3");
    model.addColumn("Col4");
    model.addColumn("Col5");
    
    int indexRow = table.getSelectedRow();
    int indexColumn = table.getSelectedColumn();
    
    if (!table.editCellAt(indexRow, indexColumn)){
          System.err.println("Não foi possível habilitar a edição desta celula");
    }
 
    
    System.out.println(table.getColumnCount());
    
  }
}
Listagem 11. Usando editCellAt() com getSelectedRow()

Veja que misturamos um pouco os conceitos aprendidos neste artigo. Capturamos o índice da coluna e da linha selecionada e tentamos habilitar a mesma para a edição. Caso tudo ocorra com sucesso, a edição será realizada, caso contrário iremos mostrar uma mensagem de erro no console.

O objetivo deste artigo vai muito além de apenas aprender o uso do JTable, mas sim a construção de um projeto básico de conexão com o banco de dados e listagem de usuários cadastrados.