Создание и обработка базы данных books в DBDerby. Используя Eclipse, выполните следующие действия: 1. Создайте проект (произвольное имя) 2. Подключите к проекту окружение БД Derby и запустите сервер БД. (В случае успешного запуска сервера БД в консоли (представление Concole) появляется следующее сообщение: 3. DRDA_SecurityInstalled.I Сетевой сервер Apache Derby Network Server - 10.3.2.1 - (599110) запущен и готов принимать соединения на порту 1527 4. Создайте базу данных в DBDerby, с помощью следующего скрипта. DROP TABLE authorISBN; DROP TABLE titles; DROP TABLE authors; connect 'jdbc:derby://localhost:1527/books;create=true;user=me;password=mine'; DROP TABLE authorISBN; DROP TABLE titles; DROP TABLE authors; CREATE TABLE authors ( authorID INT NOT NULL GENERATED ALWAYS AS IDENTITY, firstName varchar (20) NOT NULL, lastName varchar (30) NOT NULL, PRIMARY KEY (authorID) ); CREATE TABLE titles ( isbn varchar (20) NOT NULL, title varchar (100) NOT NULL, editionNumber INT NOT NULL, copyright varchar (4) NOT NULL, PRIMARY KEY (isbn) ); CREATE TABLE authorISBN ( authorID INT NOT NULL, isbn varchar (20) NOT NULL, FOREIGN KEY (authorID) REFERENCES authors (authorID), FOREIGN KEY (isbn) REFERENCES titles (isbn) ); INSERT INTO authors (firstName, lastName) VALUES ('Harvey','Deitel'), ('Paul','Deitel'), ('Andrew','Goldberg'), ('David','Choffnes'); SELECT * FROM authors; INSERT INTO titles (isbn,title,editionNumber,copyright) VALUES ('0131869000','Visual Basic 2005 How to Program',3,'2006'), ('0131525239','Visual C# 2005 How to Program',2,'2006'), 1 ('0132222205','Java How to Program',7,'2007'), ('0131857576','C++ How to Program',5,'2005'), ('0132404168','C How to Program',5,'2007'), ('0131450913','Internet & World Wide Web How to Program',3,'2004'), ('0131828274','Operating Systems',3,'2004'); INSERT INTO authorISBN (authorID,isbn) VALUES (1,'0131869000'), (2,'0131869000'), (1,'0131525239'), (2,'0131525239'), (1,'0132222205'), (2,'0132222205'), (1,'0131857576'), (2,'0131857576'), (1,'0132404168'), (2,'0132404168'), (1,'0131450913'), (2,'0131450913'), (3,'0131450913'), (1,'0131828274'), (2,'0131828274'), (4,'0131828274'); disconnect; exit; 5. Выполните два ниже прокомментированных примера. Задание: необходимо реализовать представление recordset из вашей предметной области в таблице JTable. Обработка базы данных с помощью JDBC В этом упражнении рассматривается 2 примера. В первом примере будет рассмотрено установление соединения с базой данных и выполнение запросов к ней. Второй пример показывает, как представляются результаты запроса в JTable. Установление соединения с базой данных и запросы к ней Пример, представленный ниже, выполняет простой запрос к базе данных базе данных books, который выводит все записи из таблицы authors и показывает их данные. Программа иллюстрирует соединение с базой данных, выполнение запроса и обработку результатов запроса. Затем обсуждаются основные аспекты работы программы, связанные с применением JDBC. Вывод содержимого таблицы authors. 1 2 3 // DisplayAuthors.java // Displaying the contents of the authors table. import java.sql.Connection; 2 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import import import import import java.sql.Statement; java.sql.DriverManager; java.sql.ResultSet; java.sql.ResultSetMetaData; java.sql.SQLException; public class DisplayAuthors { // database URL static final String DATABASE_URL = "jdbc:mysql://localhost/books"; // launch the application public static void main( String args[] ) { Connection connection = null; // manages connection Statement statement = null; // query statement ResultSet resultSet = null; // manages results // connect to database books and query database try { // establish connection to database connection = DriverManager.getConnection( DATABASE_URL, "javafp", "javafp" ); // create Statement for querying database statement = connection.createStatement(); // query database resultSet = statement.executeQuery( "SELECT authorID, firstName, lastName FROM authors" ); // process query results ResultSetMetaData metaData = resultSet.getMetaData(); int numberOfColumns = metaData.getColumnCount(); System.out.println( "Authors Table of Books Database:\n" ); for ( int i = 1; i <= numberOfColumns; i++ ) System.out.printf( "%-8s\t", metaData.getColumnName( i ) ); System.out.println(); while ( resultSet.next() ) { for ( int i = 1; i <= numberOfColumns; i++ ) System.out.printf( "%-8s\t", resultSet.getObject( i ) ); System.out.println(); } // end while } // end try catch ( SQLException sqlException ) { sqlException.printStackTrace(); } // end catch finally // ensure resultSet, statement and connection are closed { try { resultSet.close(); statement.close(); connection.close(); } // end try catch ( Exception exception ) 3 65 66 67 68 69 70 { exception.printStackTrace(); } // end catch } // end finally } // end main } // end class DisplayAuthors Authors Table of Books Database: authorID 1 2 3 4 firstName Harvey Paul Andrew David lastName Deitel Deitel Goldberg Choffnes Строки 3–8 импортирую интерфейсы и классы JDBC из пакета java.sql , используемого в программе. Строка 13 объявляет константу string для URL базы данных. При этом идентифицируется имя базы данных для соединения, и информацию о протоколе, используемую драйвером JDBC. Метод main (строки 16–69) соединяют с базой данных books, выполняет запрос к базе данных, выводит результат запроса и закрывает соединение с базой данных. После кодирования программы вы можете выполнить приложение, используя Eclipse. Строки 26–27 создают объект Connection (java.sql) с именем connection. Объект, который реализует интерфейс Connection управляет соединением между программой Java и базой данных. Объект Connection позволяет программам создавать утверждения SQL, которые обрабатывают базу данных. Программа инициализирует connection в результате вызова static метода getConnection класса DriverManager (java.sql), который пытается соединиться с базой данных, специфицированной URL. Метод getConnection принимает три аогумента—String , которая специфицирует URL базы данных, String , которая специфицирует имя пользователя и String , которая специфицирует пароль. URL указывает размещение базы данных (возможно в сети или в локальной файловой системе компьютера). Варианты кодирования URL для нескольких популярных СУБД показаны в таблице ниже. Если DriverManager не может соединиться с базой данных, то метод getConnection вызывает SQLException (java.sql). форматы URL для популярных СУБД для JDBC RDBMS Database URL format MySQL jdbc:mysql://hostname:portNumber/databaseName 4 форматы URL для популярных СУБД для JDBC RDBMS Database URL format ORACLE jdbc:oracle:thin:@hostname:portNumber:databaseName DB2 jdbc:db2:hostname:portNumber/databaseName Java DB/Apache jdbc:derby:dataBaseName (встроенный) Derby jdbc:derby://hostname:portNumber/databaseName (сетевой) Microsoft SQL Server jdbc:sqlserver://hostname:portNumber;databaseName=dataBaseName Sybase jdbc:sybase:Tds:hostname:portNumber/databaseName Строка 30 вызывает метод createStatement объекта Connection для получения объекта, который реализует интерфейс Statement (package java.sql). Программа использует объект Statement для того, чтобы представлять SQL в базу данных. Строки 33–34 используют метод executeQuery объекта Statement object's для предъявления запроса, который выбирает информацию обо всех авторах из таблицы authors. Этот метод возвращает объект, который реализует интерфейс ResultSet и содержит результаты запроса. Метод ResultSet позволяет программе обрабатывать результаты запроса. Строки 37–50 обрабатывают ResultSet. Строка 37 получает метаданные ResultSet как объект ResultSetMetaData (java.sql). Метаданные описывают содержимое ResultSet. Программа можетможет использовать метаданные программно для получения информации об именах столбцов и типах ResultSet. Строка 38 использует метод getColumnCount объекта ResultSetMetaData для получения количества столбцов в ResultSet. Строки 41–42 показывают имена столбцов. Строки 45–50 выводит данные из каждой строке ResultSet. Во-первых, программа помещает курсор ResultSet (который указывает на обрабатываемую строку) на первую строку ResultSet с помощью метода next (строка 45). Метод next возвращает значение boolean равным true если имеется возможность установиться на следующую строку, в противном случае, возвращается false. (Дело в том, что первоначально курсор ResultSet устанавливается перед первой строкой.) Если строки в ResultSet существуют, строка 48 выводит содержимое столбца (поля) в текущей строке. Когда обрабатывается ResultSet, представляется возможным вывести каждый столбец в соответствии с его типом. Фактически, метод getColumnType объекта ResultSetMetaData возвращает целочисленную константу класса Types (java.sql), 5 показывающую тип определенного столбца. Программы могут использовать это значение в утверждении switch, чтобы вызвать метод ResultSet , возвращающий значение соответствующего типа. Если тип столбца Types.INTEGER, метод getInt объекта ResultSet возвращает значение как int. Методы ResultSet обычно принимают качестве аргумента либо номер столбца (как int) либо имя столбца (как String), показывающие какое значение столбца получать. Для простоты этот пример рассматривает каждое значение как Object. Программа получает значение каждого столбца с помощью метода getObject объекта ResultSet (строка 48) и выводит представление String для Object. Отметим, что в отличие от индексов массива, которые начинаются с 0, столбцы ResultSet нумеруются с 1. Блок finally (строки 56–68) закрывает ResultSet (строка 60), Statement (строка 61) и Connection с базой данных (строка 62). [Замечание: Строки 60–62 будут выдавать исключительную ситуацию NullPointerExceptions если объекты ResultSet, Statement или Connection не были созданы корректно. В коде вам следует проверять переменные, которые относятся к этим объектам, чтобы убедиться, что они являются null перед выполнением close.]. Также имейте в виду, что указание столбца с номером 0 для ResultSet вызывает SQLException. Выполнение запросовбазы данных books Следующий пример позволяет вводить любой запрос в в программу. Пример показывает результаты запроса в a JTable, используя объект TableModel для обеспечения вывода данных ResultSet в формате JTable. Как вам хорошо известно, JTable является компонентом swing GUI, который может быть связан с базой данных для представления результатов запроса. Класс ResultSetTableModel выполняет соединение с базой данных через TableModel и поддерживает ResultSet. Класс DisplayQueryResults создает GUI и определяет экземпляр класса ResultSetTableModel для обеспечения данных JTable. TableModel , которая поставляет данные ResultSet для JTable. 1. ResultSetTableModel.java 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // A TableModel that supplies ResultSet data to a JTable. import java.sql.Connection; import java.sql.Statement; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import javax.swing.table.AbstractTableModel; // ResultSet rows and columns are counted from 1 and JTable // rows and columns are counted from 0. When processing // ResultSet rows or columns for use in a JTable, it is // necessary to add 1 to the row or column number to manipulate // the appropriate ResultSet column (i.e., JTable column 0 is // ResultSet column 1 and JTable row 0 is ResultSet row 1). public class ResultSetTableModel extends AbstractTableModel { private Connection connection; private Statement statement; private ResultSet resultSet; 6 22 private ResultSetMetaData metaData; 23 private int numberOfRows; 24 25 // keep track of database connection status 26 private boolean connectedToDatabase = false; 27 28 // constructor initializes resultSet and obtains its meta data object; 29 // determines number of rows 30 public ResultSetTableModel( String url, String username, 31 String password, String query ) throws SQLException 32 { 33 // connect to database 34 connection = DriverManager.getConnection( url, username, password ); 35 36 // create Statement to query database 37 statement = connection.createStatement( 38 ResultSet.TYPE_SCROLL_INSENSITIVE, 39 ResultSet.CONCUR_READ_ONLY ); 40 41 // update database connection status 42 connectedToDatabase = true; 43 44 // set query and execute it 45 setQuery( query ); 46 } // end constructor ResultSetTableModel 47 48 // get class that represents column type 49 public Class getColumnClass( int column ) throws IllegalStateException 50 { 51 // ensure database connection is available 52 if ( !connectedToDatabase ) 53 throw new IllegalStateException( "Not Connected to Database" ); 54 55 // determine Java class of column 56 try 57 { 58 String className = metaData.getColumnClassName( column + 1 ); 59 60 // return Class object that represents className 61 return Class.forName( className ); 62 } // end try 63 catch ( Exception exception ) 64 { 65 exception.printStackTrace(); 66 } // end catch 67 68 return Object.class; // if problems occur above, assume type Object 69 } // end method getColumnClass 70 71 // get number of columns in ResultSet 72 public int getColumnCount() throws IllegalStateException 73 { 74 // ensure database connection is available 75 if ( !connectedToDatabase ) 76 throw new IllegalStateException( "Not Connected to Database" ); 77 78 // determine number of columns 7 79 try 80 { 81 return metaData.getColumnCount(); 82 } // end try 83 catch ( SQLException sqlException ) 84 { 85 sqlException.printStackTrace(); 86 } // end catch 87 88 return 0; // if problems occur above, return 0 for number of columns 89 } // end method getColumnCount 90 91 // get name of a particular column in ResultSet 92 public String getColumnName( int column ) throws IllegalStateException 93 { 94 // ensure database connection is available 95 if ( !connectedToDatabase ) 96 throw new IllegalStateException( "Not Connected to Database" ); 97 98 // determine column name 99 try 100 { 101 return metaData.getColumnName( column + 1 ); 102 } // end try 103 catch ( SQLException sqlException ) 104 { 105 sqlException.printStackTrace(); 106 } // end catch 107 108 return ""; // if problems, return empty string for column name 109 } // end method getColumnName 110 111 // return number of rows in ResultSet 112 public int getRowCount() throws IllegalStateException 113 { 114 // ensure database connection is available 115 if ( !connectedToDatabase ) 116 throw new IllegalStateException( "Not Connected to Database" ); 117 118 return numberOfRows; 119 } // end method getRowCount 120 121 // obtain value in particular row and column 122 public Object getValueAt( int row, int column ) 123 throws IllegalStateException 124 { 125 // ensure database connection is available 126 if ( !connectedToDatabase ) 127 throw new IllegalStateException( "Not Connected to Database" ); 128 129 // obtain a value at specified ResultSet row and column 130 try 131 { 132 resultSet.absolute( row + 1 ); 133 return resultSet.getObject( column + 1 ); 134 } // end try 135 catch ( SQLException sqlException ) 136 { 137 sqlException.printStackTrace(); 8 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 } // end catch return ""; // if problems, return empty string object } // end method getValueAt // set new database query string public void setQuery( String query ) throws SQLException, IllegalStateException { // ensure database connection is available if ( !connectedToDatabase ) throw new IllegalStateException( "Not Connected to Database" ); // specify query and execute it resultSet = statement.executeQuery( query ); // obtain meta data for ResultSet metaData = resultSet.getMetaData(); // determine number of rows in ResultSet resultSet.last(); // move to last row numberOfRows = resultSet.getRow(); // get row number // notify JTable that model has changed fireTableStructureChanged(); } // end method setQuery } // close Statement and Connection public void disconnectFromDatabase() { if ( connectedToDatabase ) { // close Statement and Connection try { resultSet.close(); statement.close(); connection.close(); } // end try catch ( SQLException sqlException ) { sqlException.printStackTrace(); } // end catch finally // update database connection status { connectedToDatabase = false; } // end finally } // end if } // end method disconnectFromDatabase // end class ResultSetTableModel Класс ResultSetTableModel Класс ResultSetTableModel расширяет класс AbstractTableModel (javax.swing.table), который реализует интерфейс TableModel. Класс ResultSetTableModel переопределяет методы getColumnClass, getColumnCount, getColumnName, getRowCount and getValueAt 9 объекта TableModel. Реализация методов по умолчанию isCellEditable и setValueAt (обеспечиваемых AbstractTableModel) объекта TableModel не переопределяются, поскольку в этом примере не поддерживается редактирование ячеек JTable. Реализация методов по умолчанию addTableModelListener и removeTableModelListener (обеспечиваемых AbstractTableModel) объекта TableModel не переопределяются, поскольку реализация этих методов в AbstractTableModel правильно добавляет и удаляет слушателей событий. Конструктор ResultSetTableModel (строки 30–46) принимает четыре аргумента String— URL на базу данных, имя пользователя, пароль и запрос по умолчанию для выполнения. Конструктор вырабатывает все исключительные ситуации, которые происходят в его теле обратно в приложение, которое создало объект ResultSetTableModel для того, чтобы приложение могло определять, как обрабатывать эти исключительные ситуации (например, сообщать от ошибке, завершать приложение). Строка 34 устанавливает соединение с базой данных. Строки 37–39 вызывают метод createStatement объекта Connection для создания объекта Statement. Этот пример использует версию метода createStatement, который принимает два аргумента – тип и параллельность результирующего набора данных. Тип результирующего набора данных (См. Таблицу внизу) определяет способен ли курсор перемещаться в обоих направлениях или только вперед, и является ли ResultSet чувствительным к изменениям, в том смысле, что отражает он (ResultSet) либо не отражает изменения в ResultSet немедленно после того, как они сделаны с помощью методов интерфейса ResultSet. Если ResultSet не чувствителен к изменениям, то запрос, который произвел ResultSet должен быть выполнен снова , чтобы отразить эти изменения. Одновременность результирующего набора данных (concurrency) определяет может ли ResultSet быть изменен с помощью методов изменения ResultSet.Этот пример использует ResultSet , который прокручивается, не чувствителен к изменениям и только для чтения. Строка 45 вызывает наш метод setQuery (строки 144–163) для выполнения запроса по умолчанию. Типы констант. ResultSet для определения ResultSet type. константа ResultSet static type Описание TYPE_FORWARD_ONLY Specifies that a ResultSet's cursor can move only in the forward direction (i.e., from the first to the last row in the ResultSet). TYPE_SCROLL_INSENSITIVE Specifies that a ResultSet's cursor can scroll in either direction and that the changes made to the ResultSet during ResultSet processing are not reflected in the ResultSet unless the program queries the database again. TYPE_SCROLL_SENSITIVE Specifies that a ResultSet's cursor can scroll in either direction and that the changes made to the ResultSet during ResultSet processing are reflected immediately in the ResultSet. 10 Константы ResultSet для задания свойств результата. ResultSet static concurrency constant Описание CONCUR_READ_ONLY Specifies that a ResultSet cannot be updated (i.e., changes to the ResultSet contents cannot be reflected in the database with ResultSet's update methods). CONCUR_UPDATABLE Specifies that a ResultSet can be updated (i.e., changes to the ResultSet contents can be reflected in the database with ResultSet's update methods). Попытка изменить ResultSet, когда драйвер базы данных не поддерживает изменение ResultSets вызывает SQLFeatureNotSupportedExceptions. Попытка переместить курсор назад в ResultSet когда драйвер базы данных не поддерживает изменение ResultSets вызывает обратный скроллинг вызывает SQLException. Метод getColumnClass (строки 49–69) возвращает объект a Class , который представляет суперкласс всех объектов в определеннoм столбце. JTable использует эту информацию для конфигурации оформителя ячейки по умолчанию и редактора ячейки в JTable. Строка 58 использует метод getColumnClassName объекта ResultSetMetaData для получения имени класса для полной спецификации определенного столбца. Строка 51 загружает класс и возвращает соответствующий объект Class. В случае исключения, catch линии 63–66 выводят стек вызовов, и строка 68 возвращает Object.class— экземпляр Class который представляет класс Object—как тип по умолчанию. [Замечание: Строка 58 использует аргумент column + 1. Аналогично массивам, номера строк и столбцов JTable отсчитываются с 0. Однако, строки и столбцы ResultSet отсчитываются с 1. Таким образом, при обработке строк или столбцов ResultSet для использования в JTable, необходимо добавлять 1 к номеру строки или столбца для обработки соответствующих строк и столбцов ResultSet.] Метод getColumnCount (строки 72–89) возвращают номер столбцов в модели рассматриваемого ResultSet. Строка 81 использует method getColumnCount метод ResultSetMetaData для получения для получения номера столбца в ResultSet. В случае исключения, строки 83–86 блока catch выводят стек вызовов, а строка 88 возвращает 0 в качестве номера столбца по умолчанию. Метод getColumnName (строки 92–109) возвращает имя столбцов в действующей модели ResultSet. Строка 101 использует метод getColumnName объекта ResultSetMetaData для 11 получения имен столбцов из ResultSet. В случае исключения, строки 103–106 блока catch выводят стек вызовов и строка 108 возвращает пустую строку в качестве имени столбца по умолчанию. Метод getRowCount (строки 112–119) возвращает номера строк в действующей модели рассматриваемого ResultSet. Когда метод setQuery (строки 144–163) выполняет запрос, он запоминает количество строк в переменной numberOfRows. Метод getValueAt (строки 122–141) возвращает Object для определенной строки и столбца в действующей модели рассматриваемого ResultSet. Строка 132 использует метод absolute объекта ResultSet для установки курсора ResultSet на заданную строку. Строка 133 использует метод getObject объекта ResultSet для получения Object в определенном столбце текущей строки. В случае исключения, строки 135–138 блока catch выводят стек вызовов и строка 140 возвращает пустую строку в качестве значения по умолчанию. Когда метод setQuery (строки 144–163) выполняет запрос и, он получает в качестве аргумента запрос для получения нового ResultSet (строка 152). Строка 155 получает ResultSetMetaData для нового ResultSet. Строка 158 использует метод last объекта ResultSet для установки положения курсора ResultSet на последнюю запись ResultSet. [Замечание: Если таблица содержит много строк, ее загрузка может оказаться медленной.] Строка 159 использует метод getRow объекта ResultSet для получения номера строки для текущей строки в ResultSet. Строка 162 вызывает метод fireTableStructureChanged (наследуемый от класса AbstractTableModel) для оповещения каждой JTable , использующей этот объект ResultSetTableModel в случае если структура модели изменяется. Это является причиной перегрузки строк и столбцов JTable на основании новых данных из ResultSet. Метод setQuery возвращает все исключения, которые происходят в теле, обратно в приложение, которое вызывает setQuery. Метод disconnectFromDatabase (строки 166–186) реализует соответсвующий метод завершения для класса ResultSetTableModel. Разработчик класса должен обеспечить public метод , который клиенты класса должны вызывать явно для того, чтобы освободить ресурсы, которые использует объект. В этом случае метод disconnectFromDatabase закрывает ResultSet, Statement и Connection (строки 173– 175), которые рассматриваются как ограниченные ресурсы. Клиенты класса ResultSetTableModel должны всегда вызывать этот метод, когда экземпляр этого класса больше не требуется. Перед высвобождением ресурсов строка 168 проверяет является ли соединение уже завершенным. Если нет, то метод выполняется. Отметим, что все другие методы в классе ResultSetTableModel вызывают IllegalStateException если connectedToDatabase равно false. Метод disconnectFromDatabase устанавливает connectedToDatabase равным false (строка 183) для обеспечения того, чтобы клиент не использовал экземпляр ResultSetTableModel после его завершения. IllegalStateException является исключением библиотек Java, которые соответствуют обнарущению этого типа ошибок. Класс DisplayQueryResults 12 Класс DisplayQueryResults реализует графический интерфейс приложения и взаимодействует с ResultSetTableModel через объект JTable. Это приложение также показывает возможности сортировки и фильтрации, введенные в Java SE 6. Показывает содержимое базы данных books. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // DisplayQueryResults.java // Display the contents of the Authors table in the books database. import java.awt.BorderLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.sql.SQLException; import java.util.regex.PatternSyntaxException; import javax.swing.JFrame; import javax.swing.JTextArea; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.JTable; import javax.swing.JOptionPane; import javax.swing.JButton; import javax.swing.Box; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.RowFilter; import javax.swing.table.TableRowSorter; import javax.swing.table.TableModel; public class DisplayQueryResults extends JFrame { // database URL, username and password static final String DATABASE_URL = "jdbc:mysql://localhost/books"; static final String USERNAME = "javafp"; static final String PASSWORD = "javafp"; // default query retrieves all data from authors table static final String DEFAULT_QUERY = "SELECT * FROM authors"; private ResultSetTableModel tableModel; private JTextArea queryArea; // create ResultSetTableModel and GUI public DisplayQueryResults() { super( "Displaying Query Results" ); // create ResultSetTableModel and display database table try { // create TableModel for results of query SELECT * FROM authors tableModel = new ResultSetTableModel( DATABASE_URL, USERNAME, PASSWORD, DEFAULT_QUERY ); // set up JTextArea in which user types queries queryArea = new JTextArea( DEFAULT_QUERY, 3, 100 ); queryArea.setWrapStyleWord( true ); queryArea.setLineWrap( true ); JScrollPane scrollPane = new JScrollPane( queryArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 13 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); // set up JButton for submitting queries JButton submitButton = new JButton( "Submit Query" ); // create Box to manage placement of queryArea and // submitButton in GUI Box boxNorth = Box.createHorizontalBox(); boxNorth.add( scrollPane ); boxNorth.add( submitButton ); // create JTable delegate for tableModel JTable resultTable = new JTable( tableModel ); JLabel filterLabel = new JLabel( "Filter:" ); final JTextField filterText = new JTextField(); JButton filterButton = new JButton( "Apply Filter" ); Box boxSouth = boxNorth.createHorizontalBox(); boxSouth.add( filterLabel ); boxSouth.add( filterText ); boxSouth.add( filterButton ); // place GUI components on content pane add( boxNorth, BorderLayout.NORTH ); add( new JScrollPane( resultTable ), BorderLayout.CENTER ); add( boxSouth, BorderLayout.SOUTH ); // create event listener for submitButton submitButton.addActionListener( new ActionListener() { // pass query to table model public void actionPerformed( ActionEvent event ) { // perform a new query try { tableModel.setQuery( queryArea.getText() ); } // end try catch ( SQLException sqlException ) { JOptionPane.showMessageDialog( null, sqlException.getMessage(), "Database error", JOptionPane.ERROR_MESSAGE ); // try to recover from invalid user query // by executing default query try { tableModel.setQuery( DEFAULT_QUERY ); queryArea.setText( DEFAULT_QUERY ); } // end try catch ( SQLException sqlException2 ) { JOptionPane.showMessageDialog( null, sqlException2.getMessage(), "Database error", JOptionPane.ERROR_MESSAGE ); // ensure database connection is closed 14 117 tableModel.disconnectFromDatabase(); 118 119 System.exit( 1 ); // terminate application 120 } // end inner catch 121 } // end outer catch 122 } // end actionPerformed 123 } // end ActionListener inner class 124 ); // end call to addActionListener 125 126 final TableRowSorter< TableModel > sorter = 127 new TableRowSorter< TableModel >( tableModel ); 128 resultTable.setRowSorter( sorter ); 129 setSize( 500, 250 ); // set window size 130 setVisible( true ); // display window 131 132 // create listener for filterButton 133 filterButton.addActionListener( 134 new ActionListener() 135 { 136 // pass filter text to listener 137 public void actionPerformed( ActionEvent e ) 138 { 139 String text = filterText.getText(); 140 141 if ( text.length() == 0 ) 142 sorter.setRowFilter( null ); 143 else 144 { 145 try 146 { 147 sorter.setRowFilter( 148 RowFilter.regexFilter( text ) ); 149 } // end try 150 catch ( PatternSyntaxException pse ) 151 { 152 JOptionPane.showMessageDialog( null, 153 "Bad regex pattern", "Bad regex pattern", 154 JOptionPane.ERROR_MESSAGE ); 155 } // end catch 156 } // end else 157 } // end method actionPerfomed 158 } // end annonymous inner class 159 ); // end call to addActionLister 160 } // end try 161 catch ( SQLException sqlException ) 162 { 163 JOptionPane.showMessageDialog( null, sqlException.getMessage(), 164 "Database error", JOptionPane.ERROR_MESSAGE ); 165 166 // ensure database connection is closed 167 tableModel.disconnectFromDatabase(); 168 169 System.exit( 1 ); // terminate application 170 } // end catch 171 172 // dispose of window when user quits application (this overrides 173 // the default of HIDE_ON_CLOSE) 174 setDefaultCloseOperation( DISPOSE_ON_CLOSE ); 175 176 // ensure database connection is closed when user quits application 15 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 addWindowListener( new WindowAdapter() { // disconnect from database and exit when window has closed public void windowClosed( WindowEvent event ) { tableModel.disconnectFromDatabase(); System.exit( 0 ); } // end method windowClosed } // end WindowAdapter inner class ); // end call to addWindowListener } // end DisplayQueryResults constructor // execute application public static void main( String args[] ) { new DisplayQueryResults(); } // end main } // end class DisplayQueryResults 16 Строки 27–29 и 32 объявляют URL, имя пользователя, пароль и запроспо умолчанию, который передается в конструктор ResultSetTableModel для того, чтобы установить начальное соединение с базой данных и выполнить запрос по умолчанию. Конструктор DisplayQueryResults (строки 38–189) создает объект ResultSetTableModel и графический интерфейс приложения. Строка 68 создает объект JTable и передает объект в конструктор JTable, который затем регистрирует JTable в качестве слушателя (listener) для TableModelEvent, сгенерированного ResultSetTableModel. Строки 85–124 регистрируют обработчик для кнопки submitButton , которую пользователь нажимает для отправления запроса в базу данных. Когда пользователь нажимает на кнопку, то метод actionPerformed (строки 90–122) вызывает метод setQuery в классе ResultSetTableModel для выполнения нового запроса. Если запрос пользователя некорректный (например, содержит синтаксические ошибки), строки 107– 108 выполняют запрос по умолчанию. Если и этот запрос некорректен, предполагается более серьезная ошибка, и строка 117 обеспечивает закрытие соединения с базой данных, а строка 119 – выход из программы. Скриншоты экранов, представленные выше показывают результаты двух запросов. Первый экран показывает запрос по умолчанию, который возвращает все данные из таблицы authors базы данных books. Второй экран показывает имя и фамилию каждого автора из таблицы authors и дополнительно выводит название и номер издания из таблицы titles. Попытайтесь ввести свои собственные запросы и выполнить их, нажав кнопку Submit Query. В Java SE 6, JTables в настоящее время позволяет пользователям сортировать строки в соответствии с данными определенного столбца. Строки 126–127 используют класс TableRowSorter (из пакета javax.swing.table) для создания объекта, который использует ваш объект ResultSetTableModel для сортировки строк в JTable, которые представляются в результате запроса. Когда пользователь кликает на заголовок некоторого столбца JTable, то TableRowSorter взаимодействует с моделью TableModel, для переупорядочения строк на основе данных этого столбца.. Строка 128 использует метод setRowSorter объекта JTable для определения TableRowSorter для resultTable. Также JTable сожжет в настоящее время показывать подмножество данных из действующей модели TableModel. Это рассматривается как фильтрация данных. Строки 133–159 регистрируют обработчик событий для кнопки filterButton кот для выполнения фильтрации данных. В методе actionPerformed (строки 137–157), строка 139 получает текст фильтра. Если пользователь не определил текст фильтрации, то строка 142 использует метод setRowFilter объекта JTable для удаления любого ранее установленного фильтра в значение null. В противном случае, строка 147–148, использует метод setRowFilter для определения RowFilter (из пакета javax.swing) на основании пользовательского ввода. Класс RowFilter обеспечивает несколько методов создания фильтров. static метод regexFilter получает String , содержащую шаблон регулярного выражения в качестве аргумента и необязательный набор индексов, который задает столбцы для фильтрации. Если индексы не определены, то просматриваются все колонки. В нашем примере пользователем вводится шаблон регулярного выражения. После установки фильтра изменяется содержание таблицы в JTable на основе отфильтрованной TableModel. 17