div.main {margin-left: 20pt; margin-right: 20pt} Ресурсы библиотеки Swing: таблица JTable
Как известно, пакет Abstract Windows Toolkit все больше отходит на задний план,
на смену ему пришла библиотека Swing, которая выгодно отличается от последней,
своими разнообразием объектов, да и выглядит это все намного симпатичнее. Так
вот из всего этого разнообразия библиотеки Swing, хотелось бы отдельной статьей
выделить таблицу JTable, она скрывает в себе очень большие возможности, о
которых я попытаюсь рассказать. К тому же, в отличие от других объектов
библиотеки Swing, JTable довольно труден в понимании, и с первого раза
непонятно, что там и к чему. В этой статье я попытаюсь рассказать о том, как
работать с помощью таблицы JTable с Базой Данных.
TableSorter sorter = new TableSorter();
// создадим объект TableSorter
// Создадим таблицу
JTable mainTable = new JTable(sorter);
// Будем использовать прокрутку
mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
// сделаем так, чтобы редактироваться сразу могла, только
//одна ячейка
mainTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// установим обработчик события, этим мы обеспечим автоматическую
// сортировку таблицы, при нажатии мышью на заголовок таблицы
sorter.addMouseListenerToHeaderInTable(mainTable);
// создадим объект JScrollPane, конструктору которого,
// передадим нашу таблицу
JScrollPane tableAggregate = new JScrollPane(mainTable);
// установим вид рамки
tableAggregate.setBorder(new BevelBorder(BevelBorder.LOWERED));
Примечание: Класс TableSorter, который в свою очередь расширяет TableMap, не входит в JDK, его нужно предварительно положить в
ту же папку, где будет находится ваш исполняемый класc.
Теперь осталось разместить, созданную нами таблицу, на форме, для этого
проделаем следующие нехитрые операции:
//создадим основную панель
JPanel mainPanel = new JPanel();
// присоеденим к этой панели нашу таблицу
mainPanel.add(tableAggregate);
// Создать окно и поместить туда mainPanel.
frame = new JFrame("TableExample2");
// этот обработчик позволит закрывать окно
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
// установим задний фон
frame.setBackground(Color.lightGray);
// теперь таблица будет видна
frame.getContentPane().add(mainPanel);
frame.pack();
// установим размер окна
frame.setBounds(200, 200, 640, 480);
Вот мы и проделали всю рутинную и неинтересную работу, теперь
прейдем к основному. А основное, как вы понимаете, это размещение данных в
таблице, причем именно тех, которые мы хотим там увидеть, что поверьте, не так
уж и легко...Да не бойтесь, разберемся, итак существует некая связка, которую
удобно комментировать по Рис1.
Рис1.
Блок View, отвечает за визуализацию, Sorter за обработку событий и
сортировку, ну а Model за все основное. Как вы уже наверное догадались, именно
Model нам и придется. Но не все так плохо, а дело в том, что уже существует
класс AbstractTableModel его то возможности нам и придется расширить. Когда мы
создадим этот класс мы сделаем следующее:
sorter.setModel(dataBase);
// dataBase это класс являющийся дочерним от AbstractTableModel
// его то мы и будем создавать
Начнем создавать класс JDBCAdapter, с целью, чтобы в нашей таблице
отображалась любая БД, которую мы бы смогли модифицировать...
// итак родительским будет класс AbstractTableModel
public class JDBCAdapter extends AbstractTableModel
{
private Connection connection;
private Statement statement;
private ResultSet resultSet;
// здесь будут содержаться названия таблиц БД
public String[] columnNames = {};
// здесь будут храниться все данные таблицы БД
private Vector rows = new Vector();
private ResultSetMetaData metaData;
public String tableName="";
public JDBCAdapter(String url, String driverName,
String user, String passwd)
{
try
{
Class.forName(driverName);
System.out.println("Opening db connection");
connection = DriverManager.getConnection
(url, user, passwd);
statement = connection.createStatement();
}
catch (ClassNotFoundException ex)
{
System.err.println
("Cannot find the database driver classes.");
System.err.println(ex);
}
catch (SQLException ex)
{
System.err.println
("Cannot connect to this database.");
System.err.println(ex);
}
}
...
Примечание: Я не претендую, на создание
класса JDBCAdapter, его уже написали до меня, и его можно найти в примерах JDK.
Правда я его модифицировал для своих целей, и добавил ему новые возможности.
Этот конструктор позволит нам установить соединение с БД, и инициализировать
поля connection и statement. Как это происходит комментировать не буду, эту
часть можно посмотреть в моей статье: Доступ к БД из
сервлета Теперь нужно написать метод, который бы делал выборку из таблицы
БД, и динамично бы это отображал бы это в JTable
public void executeQuery(String query)
{
if (connection == null || statement == null)
{
System.err.println
("There is no database to execute the query.");
return;
}
try
{
resultSet = statement.executeQuery(query);
metaData = resultSet.getMetaData();
// получим количество полей исследуемой таблицы БД
int numberOfColumns = metaData.getColumnCount();
columnNames = new String[numberOfColumns];
// получим названия этих полей и присвоим их нашему
// полю columnNames
for(int column = 0; column < numberOfColumns; column++)
{
columnNames[column] =
metaData.getColumnLabel(column+1);
}
// Начнем получать данные из БД
rows = new Vector();
while (resultSet.next())
{
Vector newRow = new Vector();
for (int i = 1; i < = getColumnCount(); i++)
{
newRow.addElement(resultSet.getObject(i));
}
// прибавим новую запись
rows.addElement(newRow);
}
// оповестим listeners о том, что таблица изменена
fireTableChanged(null);
}
catch (SQLException ex)
{
System.err.println(ex);
}
}
Данный метод является вполне универсальным, с той точки зрения, что
ему все равно сколько полей у таблицы БД, и какого они типа, что получается при
его выполнении можно видеть на Рис2.
Рис2.
Теперь надо научить нашу таблицу изменять данные, для этого мы должны
переопределить методы класса AbstractTableModel. Следующий метод позволит
редактировать ячейки.
public boolean isCellEditable(int row, int column)
{
try
{
return metaData.isWritable(column+1);
}
catch (SQLException e)
{
return false;
}
// или просто, если не заботиться о безопасности
// return true;
}
Возвращает количество полей таблицы
public int getColumnCount()
{
return columnNames.length;
}
Возвращает количество записей
public int getRowCount()
{
return rows.size();
}
Возвращает значение ячейки
public Object getValueAt(int aRow, int aColumn)
{
Vector row = (Vector)rows.elementAt(aRow);
return row.elementAt(aColumn);
}
Теперь переопределим самый главный метод, суть которого будет
заключаться в том, чтобы изменить значения вектора rows, ну и конечно же
изменить саму базу данных.
public void setValueAt(Object value, int row, int column)
{
try
{
// Эта функция
// возвращает название поля по его номеру
String columnName = getColumnName(column);
String query =
"UPDATE "+tableName+
" SET "+columnName+" = "+
dbRepresentation(column, value)+
" WHERE ";
// метод dbRepresentation(column, value)
// преобразует значения в строку,
// для некоторых типов
// обычное toString() не подойдет
// т.к. мы не знаем, какое поле
// таблицы является ключевым,
// мы будем осуществлять поиск по всем
// полям одновременно.
// Создадим строку, которую будем
// использовать в запросе SQL
for(int col = 0; col < getColumnCount();col++)
{
String colName = getColumnName(col);
if (colName.equals(""))
{
continue;
}
if (col != 0)
{
query = query + " and ";
}
query = query + colName +" = "+
dbRepresentation(col, getValueAt(row, col));
}
// запрос готов, выполним изменения
PreparedStatement pstmt =
connection.prepareStatement(query);
pstmt.executeUpdate(); // выполнить изменение
// теперь осталось, изменить значение вектора rows
Object val;
// без этой функции работать не будет,
// если в setElementAt() вставлять непосредственно
// value а не val
val = tbRepresentation(column,value);
Vector dataRow = (Vector)rows.elementAt(row);
dataRow.setElementAt(val, column);
}
catch (Exception ex)
{
System.err.println(ex);
}
}
Давайте теперь подробней остановимся на методе
tbRepresentation(column,value) Зачем он вообще нужен? А нужен он вот для чего:
если пытаться на вход методу setElementAt(val, column) объекта dataRow подать
просто объект value, который к нам пришел из заголовка setValueAt, то возникнет
исключительная ситуация, и таблица не изменится. Это происходит из-за
несоответствия типов, который уже находится в векторе, и который мы туда
пытаемся записать. Чтобы этого не случалось и придуман метод
tbRepresentation(column,value);
public Object tbRepresentation(int column, Object value)
throws NumberFormatException
{
Object val;
int type;
if (value == null)
{
return "null";
}
try
{
type = metaData.getColumnType(column+1);
}
catch (SQLException e)
{
return value.toString();
}
switch(type)
{
case Types.BIGINT:
return val = new Long(value.toString());
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
return val = new Integer(value.toString());
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
return val = new Double(value.toString());
...
// и т.д. для всех типов SQL
}
Идея этого метода заключается в том, чтобы записывать в вектор
dataRow не просто Object, а именно объект соответствующего типа. Остался не
от комментированным лишь метод dbRepresentation(column, value), но я так думаю
вы сможете его написать и сами. Расскажу лишь смысл: любой объект можно
преобразовать в строку методом toString(), но далеко не всегда это устроит
синтаксис языка SQL. Особенно прошу обратить внимание, если вы пытаетесь
изменить в базе данных объекты типа TIMESTAMP и TIME. Чтобы разрешить эту
проблему, просто посмотрите документацию по своей БД, выясните точно, каким
образом формировать SQL запрос, для данного типа. Ну а пишите метод
dbRepresentation(column, value), который бы преобразовывал бы корректно объект в
строку.
Автор Владислав
Каменский
|