JTable+Oracle или какую пользу несут примеры
2005-04-08
Для подключений к БД из приложений написанных на JAVA компания Sun Microsystems разработала пакет драйверов JDBC
(Java DataBase Connectivity
), которые стали аналогом популярной майкрософтовской ODBC
. Собственно про JDBC
я в этой статье подробно рассказывать ничего не буду, тк на этом сайте валяется перевод спецификации JDBC от Sun, пусть и за 97-й год, но на мой взгляд там мало что изменилось, а если что, то будет повод для написания новой статьи типа "Что нового в JDBC vXXX?" Ладно в общем к делу...
Незнаю как вы а вот я, например, если что-то захотел реализовать в какой-нить среде какой-нить проект и не хера в нем (проекте) не понимаю, то сразу ищу помощи в примерах поставляемые вместе с продуктом! Да, да я имею ввиду те самые папочки с надписями типа demo, samples ... и только после этого лезу с глупыми вопросами в форумы и пост-группы. Жаль что большинство новичков этим пренебрегает, а между прочим содержимое этих папочек наполнено различными полезностями особенно для начинающих хотя и для профи тоже не помешало бы туда заглядывать время от времени. Вот и настал как-то в моей практике работы с ораклом день когда ну очень захотелось написать какой-нить клиент для БД на Java. Тк практики написания подобных программ у меня не было я недолго думая обратился к исходным кодам-примерам любезно поставляемые с JDK компанией Sun. Вот и наткнулся на файлик по имени JDBCAdapter.java который находится в каталоге jdk-xxx\demo\jfc\TableExample\src
. Это небольшой класс(хотя кто привык писать проги уровня хелло ворлд видимо будет большой) который демонстрирует возможность подключения к БД через драйвер JDBC
( кстати если кто незнает отмечу что JDBC
позволяет подключаться к практически любой СУБД, а не только к Oracle
) с возможностью выборки данных.
Итак что представляет из себя данный класс. А он (класс имеет имя - JDBCAdapter
) представляет из себя рассширение класса AbstractTableModel
- класса который представляет из себя что-то типа абстрактной модели всех процессов происходящих с JTable
(если кто не знает это класс библиотеки Swing
для работы с таблицами в Java). Класс данный в примере нужно использует объект класса таблицы JTable
например:
JDBCAdapter adapter=new JDBCAdapter(параметры); JTable table=new JTable(adapter);
Примечание: Хочу вас предупредить что примеры - это не панацея от всех ваших проблем, примеры - это правильный путь по которому вы должны двигаться чтобы написать серьезный продукт.
Для обзора данного примера я избрал пошаговый метод разбора листинга класса JDBCAdapter
, тк считаю его наиболее эффективным для понимания всех процессов.
Как-всегда код начинается с объявления импортируемых библиотек и классов:
//Класс Vector нам понадобится для хранения выбранных данных из БД
import java.util.Vector;
import java.sql.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelEvent;
Здесь мы объявляем наш класс как расширение AbstractTableModel:
public class JDBCAdapter extends AbstractTableModel { // Т.к. это не руководство JDBC, а значит по //поводу данных объектов обращайтесь спецификации JDBC Connection connection; Statement statement; ResultSet resultSet; ResultSetMetaData metaData; //Наименования столбцов таблицы String[] columnNames = {}; //Объявляем вектор строк таблицы Vector rows = new Vector(); 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 выполняет функцию установления соединения с СУБД параметрами которого являются:
String url
- url базы данных например "jdbc:oracle:тип_драйвера:@хост:1521:имя_БД"
String driverName
- для подключения к ораклу используем следующую запись: "oracle.jdbc.driver.OracleDriver"
String user, passwd
- имя учетной записи и пароль оракла
public void executeQuery(String query) { if (connection == null || statement == null) { System.err.println("There's no database to execute the query."); return; } try { //Отправляем запрос на СУБД resultSet = statement.executeQuery(query); // Получаем метаданные с сервера с помощью которых: metaData = resultSet.getMetaData(); //сохраняем кол-во столбцов int numberOfColumns = metaData.getColumnCount(); columnNames = new String[numberOfColumns]; for(int column = 0; column < numberOfColumns; column++) { //Заносим в массив имена столбцов columnNames[column] = metaData.getColumnLabel(column+1); } rows = new Vector(); //(1) while (resultSet.next()) { //(1) Vector newRow = new Vector(); //(1) for (int i = 1; i <= getColumnCount(); i++) { //(1) newRow.addElement(resultSet.getObject(i)); //(1) } //(1) rows.addElement(newRow); //(1) } //Обновляем таблицу fireTableChanged(null); } catch (SQLException ex) { System.err.println(ex); } }
Данный метод осуществляет выборку данных, которые помещает в объект класса Vector
. Самое интересное в этом методе так это его независимость от кол-ва столбцов и строк таблицы БД что делает его вполне универсальным.
(1) Мы создаем объект класса Vector rows
в который мы будем заносить значения строк таблицы, после чего запускаем цикл while условием которого является наличие строки в таблице для организации пошагового прохода по строкам таблицы. В теле цикла while создаем еще один вектор newRow и с помощью цикла for заносим значения каждого столбца текущей строки в вектор newRow, которые в свою очередь заносим в вектор rows. В итоге получаем вектор векторов:
rows[newRow[столбец1, столбец2,...], newRow[столбец1, столбец2,...],...]
- каждый элемент rows - является набором строк, а каждый элемент newRow - значение столбца определенной строки. Для доступа к значению ячейки таблицы использум следующую запись:
Vector row = rows.elementAt(номер_строки); значение_ячейки = row.elementAt(номер_колонки);
Именно эта запись используется в методе getValueAt() (см далее).
public void close() throws SQLException { System.out.println("Closing db connection"); resultSet.close(); statement.close(); connection.close(); }
Данный метод закрывает текущий сеанс с СУБД.
public String getColumnName(int column) { //(2) if (columnNames[column] != null) { //Возвращаем имя столбца return columnNames[column]; } else { return ""; } } public boolean isCellEditable(int row, int column) { //(3) try { //вернет true если ячейка редактируемая, иначе false return metaData.isWritable(column+1); } catch (SQLException e) { return false; } } public int getColumnCount() { //(4) return columnNames.length; } public int getRowCount() { //(5) return rows.size(); } public Object getValueAt(int aRow, int aColumn) { //(6) // Получаем вектор столбцов строки aRow Vector row = (Vector)rows.elementAt(aRow); return row.elementAt(aColumn); }
Метод 2 возвращает имя столбца по его номеру column. Этот метод использует JTable
чтобы дать имена заголовкам столбцов таблицы. 3 метод проверяет возможна ли редактирование ячейки таблицы. 4 и 5 методы возвращают кол-во строк и столбцов соответственно. А вот шестой метод вернет нам сами значения столбца aColumn
строки aRow
.
public Class getColumnClass(int column) {
int type;
try {
type = metaData.getColumnType(column+1);
}
catch (SQLException e) {
return super.getColumnClass(column);
}
switch(type) {
//Char, Varchar и Longvarchar будут String
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
return String.class;
case Types.BIT:
return Boolean.class;
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
return Integer.class;
case Types.BIGINT:
return Long.class;
case Types.FLOAT:
case Types.DOUBLE:
return Double.class;
case Types.DATE:
return java.sql.Date.class;
default:
return Object.class;
}
}
Этот метод будет конвертировать тип данных СУБД в тип данных поддерживаемые Java.
public String dbRepresentation(int column, Object value) { int type; if (value == null) { return "null"; } try { // Получаем тип значения столбца type = metaData.getColumnType(column+1); } catch (SQLException e) { return value.toString(); } switch(type) { case Types.INTEGER: case Types.DOUBLE: case Types.FLOAT: // Integer, double и float возвращаем как строку return value.toString(); case Types.BIT: return ((Boolean)value).booleanValue() ? "1" : "0"; case Types.DATE: // Т.к. в Java нет такого типа как Дата значит //возвращаем строку return value.toString(); default: // Для всех остальных типов возвращаем строку //заключенную в "\" return "\""+value.toString()+"\""; } }
Данный метод вернет нам тип значений определенного столбца. Тк значение каждой ячейки таблицы имеет тип Object
, то мы реализуем метод который будет возвращать нам тип нашего значения.
public void setValueAt(Object value, int row, int column) { try { //Получаем имя таблицы которую будем использовать в запросе String tableName = metaData.getTableName(column+1); if (tableName == null) { System.out.println("Table name returned null."); } //Сохраняем имя столбца которую будем использовать в запросе String columnName = getColumnName(column); //Формируем текст запроса String query = "update "+tableName+ " set "+columnName+ " = "+dbRepresentation(column, value)+ " where "; //Формируем часть запроса where 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)); } System.out.println(query); System.out.println("Not sending update to database"); //statement.executeQuery(query); } catch (SQLException e) { // e.printStackTrace(); System.err.println("Update failed"); } // Естественно обновляем наш вектор Vector dataRow = (Vector)rows.elementAt(row); dataRow.setElementAt(value, column); } }
Данный метод вызывается всякий раз когда мы будем пытаться редактировать строку в таблице JTable
.
Параметры setValueAt()
:
Object value
- новое значение
int row и int column
- строка и столбец редактируемой строки
Вот и весь класс. Теперь при использовании его с JTable
:
JDBCAdapter adapter=new JDBCAdapter(параметры); JTable table=new JTable(adapter); adapter.executeQuery("select * from scott.emp");
получим работающую таблицу на форме:
Стоит еще раз отметить что данный класс в принципе универсальный но чтобы его использовать в сложных приложениях нужно ему добавить некоторые возможности для поддержки дополнительных функций, например добавление новой строки. Думаю я показал вам насколько полезную информацию несут примеры которым мы очень часто не уделяем должного внимания а ведь есть на что посмотреть не правда ли! Старт дан, действуйте дальше!
ЗЫ как всегда жду всех ваших замечаний а также предложений и вопросов на e-mail CBAT@mail.ru. И еще если среди вас найдутся такие индивидумы которые готовы поделиться своими личными эффективными способами или методами решения той или иной задачи связанной с Ораклом или Javой обязательно пишите - обязательно опубликуем!