Назад | Начало урока | Вперед
Содержание

Глава 8

Модель таблицы для работы с базами данных

(Фрагмент главы 15 из книги Ивана Портянкина "Swing и эффективные пользовательские интерфесы")

Идея разделения данных и места, где эти данные обрабатываются давно признана удачной (в
меньшем масштабе эта идея прекрасно прижилась в библиотеке Swing, в которой разделены
модели, представляющие собой данные и виды, эти данные отображающие.

Лучше всего для отображения результатов запросов к БД подходит именно таблица JTable.
Она с легкостью отобразит любое количество столбцов и строк, позволит задать различные
типы данных для столбцов, соответствующим образом редактировать и отображать их (а в
современных БД типы хранимой информации задаются именно для столбцов), при
необходимости внести в данные изменения и передать модифицированные результаты обратно
в хранилище.

Самое приятное, что благодаря интерфейсу TableModel мы можем один раз создать модель,
получающую инф из БД, а затем использовать ее для вывода в таблицах JTable результатов
запросов к разнообразным БД.

Работать с БД мы будем при помощи интерфейса JDBC, в котором результат запроса к БД возвращается в
виде объекта ResultSet. С помощью этого объекта несложно получить данные любого типа,
считывая их по строкам и столбцам. Для каждого объекта ResultSet существует
дополнительная информация в виде объекта ResultSetMetaData, позволяющая выяснить
количество, типы и названия столбцов, полученных в результате запроса. Мы используем
эту инф для настройки нашей модели. Приступим:


// com/porty/swing/DatabaseTableModel.java
// Модель данных таблицы, работающая
// с запросами к базам данных
package com.porty.swing;

import javax.swing.*;
import javax.swing.table.*;
import java.sql.*;
import java.util.*;

public class DatabaseTableModel extends AbstractTableModel {

// здесь мы будем хранить названия столбцов
private ArrayList columnNames = new ArrayList();
// список типов столбцов
private ArrayList columnTypes = new ArrayList();
// хранилище для полученных данных из базы данных
private ArrayList data = new ArrayList();

// конструктор позволяет задать возможность редактирования
public DatabaseTableModel(boolean editable) {
this.editable = editable;
}
private boolean editable;
// количество строк
public int getRowCount() {
synchronized (data) {
return data.size();
}
}
// количество столбцов
public int getColumnCount() {
return columnNames.size();
}
// тип данных столбца
public Class getColumnClass(int column) {
return (Class)columnTypes.get(column);
}
// название столбца
public String getColumnName(int column) {
return (String)columnNames.get(column);
}
// данные в ячейке
public Object getValueAt(int row, int column) {
synchronized (data) {
return ((ArrayList)data.get(row)).get(column);
}
}
// возможность редактирования
public boolean isEditable(int row, int column) {
return editable;
}
// замена значения ячейки
public void setValueAt( Object value, int row, int column){
synchronized (data) {
((ArrayList)data.get(row)).set(column, value);
}
}
// получение данных из объекта ResultSet
public void setDataSource( ResultSet rs) throws Exception {
// удаляем прежние данные
data.clear();
columnNames.clear();
columnTypes.clear();
// получаем вспомогательную информацию о столбцах
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for ( int i=0; i < columnCount; i++) {
// название столбца
columnNames.add(rsmd.getColumnName(i+1));
// тип столбца
Class type =
Class.forName(rsmd.getColumnClassName(i+1));
columnTypes.add(type);
}
// сообщаем об изменениях в структуре данных
fireTableStructureChanged();
// получаем данные
while ( rs.next() ) {
// здесь будем хранить ячейки одной строки
ArrayList row = new ArrayList();
for ( int i=0; i < columnCount; i++) {
if (columnTypes.get(i) == String.class)
row.add(rs.getString(i+1));
else
row.add(rs.getObject(i+1));
}
synchronized (data) {
data.add(row);
// сообщаем о прибавлении строки
fireTableRowsInserted( data.size()-1, data.size()-1);
}
}//end while
}//end setDataSource
}

Результат :

Анализ

Мы назвали нашу модель DatabaseTableModel и разместили ее в пакете com.porty.swing,
так что при необходимости вы можете включать ее в программы и без особых трудностей
наглядно выводить результаты запросов к БД.

Основной метод нашей модели - setDataSource. Ему передается в параметр объект
ResultSet - результат запроса к БД. Вначале метод удаляет хранящиеся в модели данные,
полученные после обработки предыдущих запросов (если таковые были), и заново описывает
структуру столбцов таблицы на основе типов данных полученного запроса.

Все данные, включая названия и типы данных (объекты Class) столбцов, хранятся в списках
ArrayList. Итак, удаляем из списков всю прежнюю информацию и приступаем к обработке
объекта ResultSet. Как уже было сказано выше с каждым объектом ResultSet ассоциирован
другой объект ResultSetMetaData, содержащий вспомогательную инф о результатах запроса.
Он позволит нам гибко, не вдаваясь в подробности того, к какой БД мы подключены,
узнавать все необходимое для построения нашей модели.

// удаляем прежние данные
data.clear();
columnNames.clear();
columnTypes.clear();

// получаем вспомогательную информацию о столбцах
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for ( int i=0; i < columnCount; i++) {

// название столбца
columnNames.add(rsmd.getColumnName(i+1));
// тип столбца
Class type =
Class.forName(rsmd.getColumnClassName(i+1));
columnTypes.add(type);
}

Прежде всего мы получаем количество столбцов с помощью метода getColumnCount() и для
каждого столбца выясняем его название (это название столбца в БД, чаще всего оно не
слишком удобно для прочтения его человеком, но в реальном приложении вы сможете легко
сменить идентификаторы столбцов на что-либо более подходящее) и тип (тип как всегда в
ДЖ аналогичен классу, так что мы получаем название класса и создаем его экземпляр
статическим методом forName()).

Обратите внимание, что при получении инф из объекта ResultSetMetaData( а затем из объекта
ResultSet) отсчет столбцов ведется с единицы, а не с нуля).

Как только мы получаем вся подноготную новых столбцов таблицы, мы вызываем метод
fireTableStructureChanged(), который оповестит слушателей о том, что структура столбцов
таблицы поменялась. Такое оповещение приведет к отображению на экране ( в том случае
если модели присоединяется к виду, то есть в данном случае к таблице JTable), и
пользователь увидит столбцы, которые присутствуют в новом наборе данных.

// сообщаем об изменениях в структуре данных
fireTableStructureChanged();

Далее можно приступить непосредственно к работе с данными.
Для каждой строки с данными создается свой отдельный список ArrayList, в него будут
поочередно добавляться значения данных всех столбцов текущей строки запроса.

// получаем данные
while ( rs.next() ) {
// здесь будем хранить ячейки одной строки
ArrayList row = new ArrayList();
for ( int i=0; i < columnCount; i++) {
if (columnTypes.get(i) == String.class)
row.add(rs.getString(i+1));
else
row.add(rs.getObject(i+1));
}
Заметьте, что со строками дело обстоит не совсем обычно: их лучше получать специальным
методом getString(), в противном случае у вас могут возникнуть проблемы с кодировкой
нелатинских символов (Столбцы со строковыми данными имеют тип String.class).

Остальные данные можно просто получить методом getObject(), пригодным на все случаи жизни.

После заполнения данными очередной строки, она добавляется в список всех данных data, и
модель оповещает виды о том, что в ней появилась новая строка (!Оповещает!)(если вид
есть, он немедленно отобразит новую строку).


synchronized (data) {
data.add(row);
// сообщаем о прибавлении строки
fireTableRowsInserted(
data.size()-1, data.size()-1);
}
}//end while

Еще раз весь метод:


// получение данных из объекта ResultSet
public void setDataSource( ResultSet rs) throws Exception {
// удаляем прежние данные
data.clear();
columnNames.clear();
columnTypes.clear();
// получаем вспомогательную информацию о столбцах
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for ( int i=0; i < columnCount; i++) {
// название столбца
columnNames.add(rsmd.getColumnName(i+1));
// тип столбца
Class type =
Class.forName(rsmd.getColumnClassName(i+1));
columnTypes.add(type);
}
// сообщаем об изменениях в структуре данных
fireTableStructureChanged();
// получаем данные
while ( rs.next() ) {
// здесь будем хранить ячейки одной строки
ArrayList row = new ArrayList();
for ( int i=0; i < columnCount; i++) {
if (columnTypes.get(i) == String.class)
row.add(rs.getString(i+1));
else
row.add(rs.getObject(i+1));
}
synchronized (data) {
data.add(row);
// сообщаем о прибавлении строки
fireTableRowsInserted(
data.size()-1, data.size()-1);
}
}
}//end setDataSource

Остальные методы класса DatabaseTableModel нам прекрасно знакомы и служат для правильной
работы модели. Большая часть из них получает информацию (название столбца, количество
строк и столбцов, тип столбца) из списков ArrayList, которые заполняются данными в уже
рассмотренном нами методе setDataSource().

Нетрудно видеть, что все операции со списком data, хранящим основные данные запроса,
проводятся в блоке синхронизации. Список ArrayList не обладает встроенной поддержкой
синхронизации (для простоты работы и лучшего быстродействия), но в нашем случае его
вполне могут зарпашивать несколько потоков одновременно (например поток рассылки
событий, когда таблица обновляет свое изображение, и поток работающий с БД), так что во
избежание трудно обнаруживаемых ошибок работу с ним лучше проводить в блоках синхронизации.

Конструктор нашей модели позволяет указать будет ли она разрешать редактирование.


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz