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:
- Nome do Banco: dbusuario
- Nome do Usuário: root
- 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
);
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; }
}
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;
}
}
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;
}
}
Vamos entender melhor cada um dos nossos métodos apresentados:
- 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();
- deleteByLogin(): Deleta um determinado usuário de acordo com o login passado.
- getUsuarios(): É um dos nossos métodos principais, pois é quem irá popular nossa JTable. Este retorna uma lista de Usuários.
- 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.
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()
});
}
}
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);
}
}
}
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:
- Removendo a linha do JTable;
- 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" });
}
}
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" });
}
}
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();
}
}
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());
}
}
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());
}
}
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.