Печать страницы

Java и DB: JTable+Oracle или какую пользу несут примеры

Автор: Swat
Опубликовано: 08-04-05

Для подключений к БД из приложений написанных на JAVA компания Sun Microsystems разработала пакет драйверов JDBC (Java DataBase Connectivity), которые стали аналогом популярной майкрософтовской ODBC. Собственно про JDBC я в этой статье подробно рассказывать ничего не буду, тк на этом сайте валяется перевод спецификации JDBC от Sun, пусть и за 97-й год, но на мой взгляд там мало что изменилось, а если что, то будет повод для написания новой статьи типа "Что нового в JDBC vXXX?" Ладно в общем к делу...

Незнаю как вы а вот я, например, если что-то захотел реализовать в какой-нить среде какой-нить проект и не хера в нем (проекте) не понимаю, то сразу ищу помощи в примерах поставляемые вместе с продуктом! Да, да я имею ввиду те самые папочки с надписями типа demo, samples ... и только после этого лезу с глупыми вопросами в форумы и пост-группы. Жаль что большинство новичков этим пренебрегает, а между прочим содержимое этих папочек наполнено различными полезностями особенно для начинающих хотя и для профи тоже не помешало бы туда заглядывать время от времени. Вот и настал как-то в моей практике работы с ораклом день когда ну очень захотелось написать какой-нить клиент для БД на Java. Тк практики написания подобных программ у меня не было я недолго думая обратился к исходным кодам-примерам любезно поставляемые с JDK компанией Sun. Вот и наткнулся на файлик по имени JDBCAdapter.java который находится в каталоге jdk-xxx\jfc\TableExample\src. Это небольшой класс(хотя кто привык писать проги уровня хелло ворлд видимо будет большой) который демонстрирует возможность подключения к БД через драйвер JDBC( кстати если кто незнает отмечу что JDBC позволяет подключаться к практически любой СУБД, а не только к Oracle) с возможностью выборки данных.

Итак что представляет из себя данный класс. А он (класс имеет имя - JDBCAdapter) представляет из себя рассширение класса AbstractTableModel - класса который представляет из себя что-то типа абстрактной модели всех процессов происходящих с JTable (если кто не знает это класс библиотеки Swing для работы с таблицами в Java). Класс данный в примере нужно использует объект класса таблицы JTable например:

JDBCAdapter adapter=new JDBCAdapter(параметры);
JTable table=new JTable(adapter);

Примечание: Хочу вас предупредить что примеры - это не панацея от всех ваших проблем, примеры - это правильный путь по которому вы должны двигаться чтобы написать серьезный продукт.

Для обзора данного примера я избрал пошаговый метод разбора листинга класса JDBCAdapter, тк считаю его наиболее эффективным для понимания всех процессов. Как-всегда код начинается с объявления импортируемых библиотек и классов:

import java.util.Vector;   //Класс Vector нам понадобится для хранения выбранных данных из БД
import java.sql.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelEvent;

Здесь мы объявляем наш класс как расширение AbstractTableModel:

public class JDBCAdapter extends AbstractTableModel {

    Connection connection;		        // Т.к. это не руководство JDBC, а значит по
    Statement statement;		        // поводу данных объектов обращайтесь спецификации JDBC
    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 is 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 {
        return metaData.isWritable(column+1);	        // вернет true если ячейка редактируемая, иначе false
    }
    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)
    Vector row = (Vector)rows.elementAt(aRow);	        // Получаем вектор столбцов строки 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) {
        case Types.CHAR:		// Char, Varchar и Longvarchar будут String
        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:
        return value.toString();	                         // Integer, double и float возвращаем как строку
    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 ";
       for(int col = 0; col < getColumnCount(); col++) {                        // Формируем часть запроса where
           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ой обязательно пишите - обязательно опубликуем!

Hosted by uCoz