Alterar e Excluir dados de JTable com AbstractModelTable no banco de dados

Olá gente!

Bem, eu já vi uns exemplos de como fazer alterações na tabela, mas ainda não consegui entender como eu consigo mudar esses dados no banco.
To tentando com uma tabela bem simples, com dois campos: o código e o nome do tipo. O código é gerado automaticamente por uma sequencia no banco de dados, então o usuário não pode alterar ele.

Eu tenho um método alterar e um excluir no meu Controller, que são assim:

public void alterar(/**int codigotipo,*/ String nometipo) throws ParseException, SQLException {
        Tipo tipo = new Tipo();
        //tipo.setCodigo(codigotipo);
        tipo.setNome(nometipo);
         new TipoDao().alterar(tipo);
    }
    
    public void excluir(String nome) throws SQLException {
       new TipoDao().excluir(nome);
    }

No meu DAO tenho as consultas:

[code]
public void alterar(Tipo tipo) throws SQLException {
String update = "UPDATE tipomedicamento " +
“SET nometipo = ?” +
“WHERE nometipo = ?”;
update(update, tipo.getNome());
}

public void excluir(String nome) throws SQLException {
    String delete = "DELETE FROM tipomedicamento WHERE nometipo = ?";
    delete(delete, nome);
}[/code]

Tenho minha TableModel, que é basicamente igual a uma que o ViniGodoy postou de exemplo aqui no GUJ, que é assim:

[code]
package tipo.model;

import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
*

  • @author Munique
    */
    public class TipoTableModel extends AbstractTableModel {
    private static final int COL_CODIGO = 0;
    private static final int COL_NOME = 1;

    private List valores;

    public TipoTableModel(List valores){
    this.valores = new ArrayList(valores);
    }

    public int getRowCount() {
    return valores.size();
    }

    public int getColumnCount() {
    return 2;
    }

    public String getColumnName(int column) {
    if (column == COL_CODIGO) return “Código”;
    if (column == COL_NOME) return “Nome do Tipo”;
    return “”;
    }

    public Object getValueAt(int row, int column) {
    Tipo nome = valores.get(row);
    if (column == COL_CODIGO) return nome.getCodigo();
    else if (column == COL_NOME) return nome.getNome();
    return “”;
    }

    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    Tipo tipo = valores.get(columnIndex);
    if (columnIndex== COL_NOME) tipo.setNome(aValue.toString());
    else if (columnIndex== COL_CODIGO) tipo.setCodigo(Integer.parseInt(aValue.toString()));
    }

    public Class<?> getColumnClass(int columnIndex) {
    return String.class;
    }

    public boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
    }

    public Tipo get(int row) {
    return valores.get(row);
    }

    /**public void getLinhaTAbela(){
    getValueAt(COL_NOME, COL_NOME);
    }*/
    }[/code]

E tenho uma view chamada ControleTipo, que fiz pelo editor de Swing do Netbeans, nela eu tenho botões para Inserir, Editar e Excluir. O Inserir tá funcionando certinho, mas estou tendo problemas com o editar e excluir. Alguém pode me ajudar?

Você tem algumas opções:

  1. Passar o seu DAO para a tabela na hora de construí-la, e deixa-la chamar o método alterar no setValueAt;
  2. Implementar na sua interface gráfica um TableModelListener, e chamar lá o DAO sempre que um evento de mudança de tabela (INSERT, DELETE ou UPDATE) for disparado;
  3. Chamar o seu DAO para todas as linhas da tabela, antes de deixar a tela (só recomendo se você tiver algum tipo de controle para saber se o dado foi realmente alterado na classe de negócio, e evitar chamar desnecessariamente o banco caso contrário).

ViniGodoy, obrigado pelas sugestões.

Eu estou tentando implementar a partir da sua primeira sugestão, passar o meu DAO para a tabela na hora de construí-la, e deixa-la chamar o método alterar no setValueAt, mas acabei esquecendo de mencionar que o botão inserir, chama uma outra interface gráfica chamada CadastroTipo e não uma nova linha na tabela. Então, minha ideia é que a quando o usuário clicar em Alterar, essa interface CadastroTipo seja chamada também, e preenchida com os dados da linha selecionada.
Acho que não vai dar certo né?

Eu li sobre o TableModelListener, mas não entendi direito.

Nesse caso é muito mais fácil:

  1. Abra um JDialog modal, passe uma cópia do tipo selecionado para a JDialog;
  2. Altere esse tipo na JDialog;
  3. Se o usuário pressionar ok no seu JDialog, chame o método alterar do seu DAO sobre esse tipo, e substitua o objeto alterado pelo que está na tabela.

Há um exemplo de como trabalhar com JDialogs aqui:
http://www.guj.com.br/java/55710-jdialog-devolvendo-valor-pra-jinternalframe#292687

O seu método do botão ficará mais ou menos assim:

[code]public void btnAlterarActionPerformed() {
int index = tblTipo.getSelectedItem();
if (index == -1)
return;

//Criamos o Dialog para a alteração
TipoTableModel model = (TipoTableModel) tblTipo.getModel();

//Estou assumindo que o construtor da JDialog faz a cópia dos valores.
CadastroTipoDialog ctd = new CadastroTipoDialog(model.get(index));
ctd.setVisible(true); //Em janelas modais, esse método trava até que ela seja fechada.

//O método isOk não existe, vc deve cria-lo na CadastroTipoDialog.
//Ele deve retornar true se o usuário pressionou ok, false caso contrário.
if (!ctd.isOk())
return;

//Alteramos o tipo no banco.
TipoDao dao = new TipoDao();
dao.alterar(tipo);
//Alteramos no JTable
model.set(index, ctd.getTipo()); //getTipo deve retornar o tipo alterado
}
[/code]

A implementação do método set, no seuTableModel seria assim:

public void set(int row, Tipo tip) { tipos.set(row, tipo); fireTableRowsUpdated(row, row); }

Valeu Vini!

Você disse que funciona com JDialog e JFrame, certo? É que estou usando o JFrame.
Não conheço bem o JDialog, vou ler sobre ele agora, qualquer coisa posto aqui, mas muito obrigado pela ajuda até agora!

Não, pq o JFrame não pode ser modal.

Recomendo que faça a troca, pois dialogs modais não podem ser fechados até que o usuário conclua sua ação.
Ele bloqueia que o usuário volte a janela anterior, o que facilita muito a programação.

Ahhh… sério? Caracas, vou ter que fazer uns punhados de mudanças no projeto então. É que no caso esse Controle Tipo é só a classe mais ‘simples’ do sistemas, além dela tenho mais 5 telas de Cadastros e mais algumas de consultas e relatórios. Não teria como fazer uma verificação no próprio JFrame?

É que, como eu disse, não entendo muito (leia aqui:nada) do JDialog. Teriam que ser feitas muitas alterações, ou ele funciona como o JFrame?
No caso, o que seria um JDialog ‘modal’?

Ele é extremamente similar ao JFrame (veja o exemplo que te passei, são praticamente idênticos).

Um JDialog modal tem a propriedade modal definida para true (geralmente é só fazer seuDialog.setModal(true)). Ele não deixa o usuário acessar outras janelas até que ele seja fechado.

Ahá!

Consegui entender mais ou menos o funcionamento. O código do da implementação da interface gráfica pode ser o mesmo, certo? Ou ao menos eu copiei e colei ele e funcionou…
Só foi ajustar de JFrame pra JDialog… Agora vou tentar o botão!

Sim, pode ser o mesmo. Isso porque todos os componentes de tela (JFrame, JDialog, JInternalFrame) seguem o mesmo princípio.

Os componentes são colocados num painel central, chamado de ContentPane.
E como todo painel é um JPanel, a forma de colocar eles lá é a mesma. :slight_smile:

[quote=ViniGodoy]O seu método do botão ficará mais ou menos assim:

[code]public void btnAlterarActionPerformed() {
int index = tblTipo.getSelectedItem();
if (index == -1)
return;

//Criamos o Dialog para a alteração
TipoTableModel model = (TipoTableModel) tblTipo.getModel();

//Estou assumindo que o construtor da JDialog faz a cópia dos valores.
CadastroTipoDialog ctd = new CadastroTipoDialog(model.get(index));
ctd.setVisible(true); //Em janelas modais, esse método trava até que ela seja fechada.

//O método isOk não existe, vc deve cria-lo na CadastroTipoDialog.
//Ele deve retornar true se o usuário pressionou ok, false caso contrário.
if (!ctd.isOk())
return;

//Alteramos o tipo no banco.
TipoDao dao = new TipoDao();
dao.alterar(tipo);
//Alteramos no JTable
model.set(index, ctd.getTipo()); //getTipo deve retornar o tipo alterado
}
[/code]

A implementação do método set, no seuTableModel seria assim:

public void set(int row, Tipo tip) { tipos.set(row, tipo); fireTableRowsUpdated(row, row); }[/quote]

Vini, eu entendi o propósito, mas essa parte:

[quote]TipoDao dao = new TipoDao(); dao.alterar(tipo);[/quote]

Isso não teria que ser chamado no Controller?

Sim, pode delegar diretamente para o controller.

Hm, acho que fiz algo errado, bem errado.

Vini, você disse que o construtor deve fazer a cópia dos valores, não é?
Não posso fazer algo como: public CadastroTipoDialog(Tipo get) { get.setNome("nometipo"); }

Acho que estou fazendo uma gambiarra total aqui, mas vai saber.

Se você fizer isso, o nome vai ser alterado independente do usuário pressionar no botão ok ou cancelar.
Talvez ele não seja alterado no banco, mas certamente seu JTable vai exibir o novo valor. Por isso geralmente é necessário criar uma cópia do valor.

Outra possibilidade seria carregar o tipo novamente do banco, antes de abrir o JDialog, e passar esse tipo carregado.

Ah sim. Não tinha nem pensando nisso.

Então vou deixar com:

CadastroTipoDialog(Tipo get) { get.getNome(); }

Eu passei isso aqui pro controller:

Tipo tipo = new Tipo(); tipo.setNome(nometipo); new TipoDao().alterar(tipo);

E no ControleTipo coloquei um ActionPerformed pro botão como você disse, tirando a parte ali de cima que coloquei no controller.
Já no CadastroTipoDialog, criei o seguinte método para getTipo:

protected Tipo getTipo() throws ParseException { TipoController tc = new TipoController(); try { tc.alterar(jTextFieldNomeTipo.getText()); JOptionPane.showMessageDialog(this, "Contato alterado com sucesso!"); } catch (SQLException e) { JOptionPane.showMessageDialog(this, "Nao foi possivel alterar contato!\n" + e.getLocalizedMessage()); } return getTipo(); }

E sei que isso tá errado, só não faço mais ideia de onde.

Não está faltando dizer ao seu controller quem precisa ser alterado. Como ele vai saber de que objeto aquele nome ali se refere?

Achei que essas linhas aqui estivesse fazendo isso:
tc.alterar(jTextFieldNomeTipo.getText());

O método alterar está no controller, então ele não vai pegar o que é pra ser alterado e o atual:

E como seu controller sabe qual objeto deve ser alterado?
Aí vc cria um novo controller, o que está ok.

Mas vc chama o método alterar, que deve modificar alguém já existente, entretanto, seu único parâmetro é o nome do tipo, que vem do JTextField.
Nesse caso, qual dos objetos já existentes receberá esse novo nome?

Estou questionando porque, normalmente, o método alterar recebe também o id de quem deve ser alterado. Até para ser possível montar uma query assim:

Ah sim… Bem, eu estava pensando em alterar só o nome mesmo e manter o mesmo ID. No caso como estou com uma sequencia, acho que ficaria:

Isso me pareceu bem estranho, mas como o usuário não pode mexer no código, acho que é a unica solução.

A minha query para Update estava assim: