venerdì 14 marzo 2008

JTable e ResultSet: visualizzazione dei risultati di una query in una tabella

Nella scrittura di un programma Java capita molto spesso di dover visualizzare i risultati di una query in una tabella. Questo breve articolo illustra un modo semplice e rapido per visualizzare i dati estratti da un database SQL in un oggetto JTable, dando la possibilità di aggiornare tali dati qualora la query cambi nel tempo.

L'idea fondamentale è quella di creare un modello di tabella, che analizzerà i risultati della query SQL (sotto forma di ResultSet) memorizzandoli internamente in una struttura di tipo Vector. Terminato ciò il modello disporrà di una immagine in memoria dei dati estratti dal database, che quindi potranno essere visualizzati dalla relativa JTable.

Di seguito il codice:

public class GenericTableModel extends DefaultTableModel implements TableModelListener{

public GenericTableModel(){
super();
this.addTableModelListener(this);
}

public GenericTableModel(ResultSet rs){
this();
// parse the result set if possible
if( rs != null )
this.parseResultSet(rs);
}


public synchronized final Object[] getRow(int rowIndex){
if( rowIndex < 0) return null;
else{
Object ret[] = new Object[ this.getColumnCount() ];
for(int i=0; i< ret.length; i++){
ret[i] = ((Vector)this.dataVector.get(rowIndex)).get(i);
}

return ret;
}

}


public synchronized void parseResultSet(ResultSet rs){
if( rs == null ){
// empty the vectors!
this.dataVector = new Vector();
this.columnIdentifiers = new Vector();
return;
}

try{

// table headers and other information
ResultSetMetaData metaData = rs.getMetaData();
int columns = metaData.getColumnCount();

Vector tmpHeaders = new Vector(columns);
//this.headers = new Vector();
for(int i=1; i<=columns; i++)
tmpHeaders.add( new String(metaData.getColumnLabel(i)));

// extract the data as String and put it into the vector
Vector tmpData = new Vector(20,5);
Vector row = null;

while( rs.next() ){
row = new Vector();

for(int i=1; i<=columns; i++)
row.add( rs.getObject(i) );

tmpData.add(row);
}
}

// notify changes to the table
this.setDataVector(tmpData, tmpHeaders);


}catch(SQLException e){
// .....
}

}



public void tableChanged(TableModelEvent e) {
int row = e.getFirstRow();
int column = e.getColumn();

// check to have a good selection, thus to avoid exception while changing the table
if( row < 0 || column < 0 )
return;

TableModel model = (TableModel)e.getSource();

String columnName = model.getColumnName(column);

Object data = model.getValueAt(row, column);

// replace the value of the data at such row and column
((Vector)this.dataVector.get(row)).set(column, data);
}

}

Come si intuisce il metodo fondamentale è parseResultSet(..), che accetta un ResultSet in ingresso e lo analizza, estrando i dati e riempendo due Vector, uno per le intestazioni della tabella e uno per le righe effettive. Una volta terminato il riempimento dei Vector, è necessario informare il modello (DefaultTableModel) di utilizzare i dati in essi contenuti per la visualizzazione; questo è ottenuto con la chiamata al metodo setDataVector(..).
Si noti che il modello consente anche di gestire le modifiche apportate ai dati attraverso la tabella stessa: il metodo tableChanged(..) dell'interfaccia implementata TableModelListener consente di catturare la riga e la colonna (cella) modificata e il suo nuovo valore. Da tenere presente che il vettore dataVector è un Vector protetto nella superclasse DefaultTableModel, e quindi accessibile. Attenzione: la modifica al modello, come si può notare, non implica automaticamente la scrittura dei nuovi dati nel database! E' necessario implementare una qualche logica di gestione della serializzazione dei dati verso il database (ad esempio usando dei ResultSet aggiornabili).

L'utilizzo del modello sopra illustrato nei propri programmi risulta piuttosto semplice:

GenericTableModel model = new GenericTableModel();
JTable table = new JTable( model );
...
model.parseResultSet( rs );

Nessun commento: