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

Swat
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ой обязательно пишите - обязательно опубликуем!

Hosted by uCoz