6. Управління базами даних за допомогою API JDBC
6.1. Загальні відомості
На ринку існує безліч баз даних. З метою уніфікації доступу до баз даних у середовищі Windows компанія Microsoft розробила інтерфейс під назвою ODBC (Open DataBase Connectivity). Цей рівень приховує особливості кожної бази даних за стандартним інтерфейсом. У Windows існує безліч драйверів, що полегшують доступ до баз даних. Ось, наприклад, список драйверів, встановлених на комп’ютері з Win95:

Програма, що використовує ці драйвери, може працювати з будь-якою з наведених вище баз даних без необхідності переробки коду.

Щоб Java-програми також могли використовувати інтерфейс ODBC, компанія Sun створила інтерфейс JDBC (Java DataBase Connectivity), який буде розміщуватися між Java-додатком та інтерфейсом ODBC:

6.2. Важливі етапи роботи з базами даних
6.2.1. Вступ
У додатку JAVA, що використовує базу даних з інтерфейсом JDBC, зазвичай виконуються такі етапи:
- Підключення до бази даних
- Відправлення запитів SQL до бази даних
- Отримання та обробка результатів цих запитів
- Закриття з’єднання
Етапи 2 і 3 виконуються повторно, а закриття з’єднання відбувається лише після завершення роботи з базою даних. Це відносно класична схема, до якої ви, можливо, вже звикли, якщо раніше працювали з базою даних в інтерактивному режимі. Ми детально розглянемо кожен із цих етапів на прикладі. Розглянемо базу даних ACCESS, яка називається ARTICLES і має таку структуру:
ім’я | тип |
code | 4-символьний код товару |
nom | назва (рядок символів) |
prix | ціна (фактична) |
stock_actu | його поточний запас (ціле число) |
stock_mini | мінімальний запас (ціле число), при недосягненні якого необхідно поповнити товар |
Ця база даних ACCESS визначена як джерело даних «користувача» у менеджері баз даних ODBC:


Її характеристики вказуються за допомогою кнопки Configurer наступним чином:

Ця конфігурація полягає, по суті, у прив’язці до бази даних ARTICLES файлу Access articles.mdb, що відповідає цій базі. Після цього база даних ARTICLES стає доступною для додатків, що використовують інтерфейс ODBC.
6.2.2. Етап підключення
Щоб працювати з базою даних, Java-додаток спочатку має виконати етап підключення. Це здійснюється за допомогою методу наступного класу:
з
DriverManager — клас Java, що містить список драйверів, доступних для програми
Connection: клас Java, що встановлює зв’язок між додатком і базою даних, завдяки якому додаток надсилатиме запити SQL до бази даних та отримуватиме результати
URL: ім’я, що ідентифікує базу даних. Це ім’я аналогічне до URL в Інтернеті. Саме тому воно входить до складу класу URL. Однак Інтернет тут ніяк не задіяний. URL має такий вигляд:
jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2
У наших прикладах, де ми використовуватимемо виключно драйвери ODBC, драйвер називається odbc. Третя частина URL складається з імені джерела та можливих параметрів. У наших прикладах це будуть джерела ODBC, відомі системі. Таким чином, URL для джерела даних Articles, визначеного раніше, буде
jdbc:odbc:Articles
id: Ідентифікатор користувача (login)
mdp: пароль користувача (password)
Підсумовуючи, програма підключається до бази даних:
- яка ідентифікується за іменем (URL)
- під обліковим записом користувача (id, mdp)
Якщо ці три параметри правильні, і якщо існує драйвер, здатний забезпечити підключення Java-додатка до вказаної бази даних, то між Java-додатком і базою даних встановлюється з’єднання. Для програми це з’єднання реалізується об’єктом типу Connection, що повертається класом DriverManager. Оскільки це з’єднання може завершитися невдачею з різних причин, воно може спричинити виняток. Тому ми напишемо:
Connection connexion=null;
URL base=...;
String id=...;
String mdp=...;
try{
connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
// обробити виняток
}
Щоб підключення до бази даних було можливим, необхідно мати відповідний драйвер. У наших прикладах це буде драйвер ODBC, здатний працювати з необхідною базою даних. Якщо цей драйвер має бути доступним у списку драйверів ODBC, що знаходяться на комп’ютері, також потрібно мати клас JAVA, який буде взаємодіяти з ним. Для цього програма запитує необхідний їй клас таким чином:
Клас Class жодним чином не пов’язаний з інтерфейсом JDBC. Це загальний клас для управління класами. Його статичний метод forName дозволяє динамічно завантажувати клас і, отже, користуватися його атрибутами та статичними методами. Клас, що взаємодіє з драйверами ODBC для Windows, називається «sun.jdbc.odbc.JdbcOdbcDriver». Отже, ми напишемо (метод може згенерувати виняток):
try{
Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
// обробити виняток (клас не існує)
}
Класи, необхідні для інтерфейсу JDBC, містяться в пакеті java.sql. Отже, на початку програми слід написати:
Ось програма, що дозволяє підключитися до бази даних:
import java.sql.*;
import java.io.*;
// виклик: pg PILOTE URL UID MDP
// підключається до бази даних URL за допомогою класу JDBC PILOTE
// користувач UID ідентифікується за допомогою пароля MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// перевірка кількості аргументів
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// підключення до бази даних
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
try{
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
} catch (Exception e){
erreur("Erreur " + e,2);
}
// закриття бази даних
try{
connect.close();
System.out.println("Base " + arg[1] + " fermée");
} catch (Exception e){}
}// головна
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// клас
Ось приклад виконання:
E:\data\java\jdbc\0>java connexion1 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles
Connexion avec la base jdbc:odbc:articles établie
Base jdbc:odbc:articles fermée
6.2.3. Відправлення запитів до бази даних
Інтерфейс JDBC дозволяє надсилати запити SQL до бази даних, підключеної до Java-додатку, а також обробляти результати цих запитів. Мова SQL (Structured Query Language) — це стандартизована мова запитів для реляційних баз даних. У ній розрізняють кілька типів запитів:
- запити на отримання даних із бази (SELECT)
- запити на оновлення бази даних (INSERT, DELETE, UPDATE)
- запити на створення/видалення таблиць (CREATE, DELETE)
Тут передбачається, що читач знайомий з основами мови SQL.
6.2.3.1. Клас Statement
Щоб відправити будь-який запит SQL до бази даних, Java-додаток повинен мати об’єкт типу Statement. Цей об’єкт, серед іншого, зберігатиме текст запиту. Цей об’єкт обов’язково пов’язаний із поточним з’єднанням. Отже, саме метод встановленого з’єднання дозволяє створювати об’єкти Statement, необхідні для відправлення запитів SQL. Якщо connexion — це об’єкт, що символізує з’єднання з базою даних, то об’єкт Statement отримується таким чином:
Отримавши об’єкт Statement, можна відправляти запити SQL. Це відбуватиметься по-різному залежно від того, чи є запит запитом на отримання даних чи на оновлення бази.
6.2.3.2. Відправлення запиту на отримання даних з бази
Запит на отримання даних зазвичай має такий вигляд:
Обов’язковими є лише ключові слова першого рядка, решта — необов’язкові. Існують також інші ключові слова, які тут не наведено.
- Здійснюється з'єднання з усіма таблицями, що знаходяться після ключового слова from
- Зберігаються лише стовпці, що знаходяться після ключового слова `select`
- Зберігаються лише ті рядки, які відповідають умові ключового слова `where`
- Отримані рядки, впорядковані відповідно до виразу ключового слова `order by`, утворюють результат запиту.
Результатом запиту select є таблиця. Якщо розглянути попередню таблицю ARTICLES і отримати назви товарів, поточний запас яких нижчий за мінімальний поріг, слід написати: *select nom from articles where stock\_actu<stock\_mini*. Якщо потрібно отримати їх у алфавітному порядку за назвами, слід написати: select name from articles where stock_actu<stock_mini order by name
Для виконання такого запиту клас Statement надає метод executeQuery:
де requête — це текст запиту SELECT, який потрібно відправити.
Отже, якщо
- connexion — це об’єкт, що символізує з’єднання з базою даних
- Statement s=connexion.createStatement() створює об’єкт Statement, необхідний для відправлення запитів SQL
- ResultSet rs=s.executeQuery(« select nom from articles where stock_actu<stock_mini ») виконує запит select і присвоює рядки результатів запиту об’єкту типу ResultSet.
6.2.3.3. Клас ResultSet: результат запиту SELECT
Об’єкт типу ResultSet представляє таблицю, тобто сукупність рядків і стовпців. У певний момент часу доступний лише один рядок таблиці, який називається поточним рядком. Під час початкового створення об’єкта ResultSet поточним рядком є рядок № 1, якщо ResultSet не є порожнім. Щоб перейти до наступного рядка, клас ResultSet має метод next:
Цей метод намагається перейти до наступного рядка ResultSet і повертає true у разі успіху, а false — у разі невдачі. У разі успіху наступний рядок стає новим поточним рядком. Попередній рядок втрачається, і повернутися назад, щоб його відновити, неможливо. Таблиця ResultSet має стовпці col1, col2,.... Для роботи з різними полями поточного рядка доступні такі методи:
для отримання поля «coli» з поточного рядка. Type позначає тип поля coli. Досить часто метод getString застосовують до всіх полів, що дозволяє отримати вміст поля у вигляді символьного рядка. Потім, за необхідності, його конвертують. Якщо назва стовпця невідома, можна скористатися методами
, де i — індекс потрібного стовпця (i>=1).
6.2.3.4. Перший приклад
Ось програма, яка виводить вміст раніше створеної бази даних ARTICLES:
import java.sql.*;
import java.io.*;
// відображення вмісту системної бази даних ARTICLES
public class articles1{
static final String DB="ARTICLES"; // база даних для обробки
public static void main(String arg[]){
Connection connect=null; // підключення до бази
Statement S=null; // об’єкт для надсилання запитів
ResultSet RS=null; // таблиця результатів запиту
try{
// підключення до бази даних
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// створення об’єкта Statement
S=connect.createStatement();
// виконання запиту select
RS=S.executeQuery("select * from " + DB);
// обробка таблиці результатів
while(RS.next()){ // поки є рядок для обробки
// її відображають на екрані
System.out.println(RS.getString("code")+","+
RS.getString("nom")+","+
RS.getString("prix")+","+
RS.getString("stock_actu")+","+
RS.getString("stock_mini"));
}// наступний рядок
} catch (Exception e){
erreur("Erreur " + e,2);
}
// закриття бази даних
try{
connect.close();
System.out.println("Base " + DB + " fermée");
} catch (Exception e){}
}// рука
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// клас
Отримано такі результати:
Connexion avec la base ARTICLES établie
a300,vélo,1202,30,2
d600,arc,5000,10,2
d800,canoé,1502,12,6
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
f450,essai3,3,3,3
f807,cachalot,200000,0,0
z400,léopard,500000,1,1
g457,panthère,800000,1,1
Base ARTICLES fermée
6.2.3.5. Клас ResultSetMetadata
У попередньому прикладі нам відомі назви стовпців у ResultSet. Якщо їх невідомо, не можна використовувати метод getType(nom_colonne). Замість цього слід використовувати getType(№ стовпця). Однак, щоб отримати всі стовпці, нам потрібно знати, скільки стовпців має отриманий файл ResultSet. Клас ResultSet не надає нам цієї інформації. Її надає клас ResultSetMetaData. У більш загальному сенсі цей клас надає інформацію про структуру таблиці, тобто про характер її стовпців.
Доступ до інформації про структуру ResultSet можна отримати, спочатку створивши екземпляр об’єкта ResultSetMetaData. Якщо RS є ResultSet, то відповідний ResultSetMetaData отримується за допомогою:
Звернемо увагу на два корисні методи в класі ResultSetMetaData:
- int getColumnCount(), що повертає кількість стовпців у ResultSet
- String getColumnLabel(int i), який повертає назву стовпця i з ResultSet (i>=1)
6.2.3.6. Другий приклад
Попередня програма виводила вміст бази даних ARTICLES. Тут ми напишемо програму, яка виконує в базі даних ARTICLES будь-який запит SQL або Select, який користувач вводить з клавіатури.
import java.sql.*;
import java.io.*;
// відображає вміст системної бази даних ARTICLES
public class sql1{
static final String DB="ARTICLES"; // база даних для обробки
public static void main(String arg[]){
Connection connect=null; // підключення до бази
Statement S=null; // об’єкт для надсилання запитів
ResultSet RS=null; // таблиця результатів запиту
String select; // текст запиту SQL select
int nbColonnes; // кількість стовпців у ResultSet
// створення потоку введення з клавіатури
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// підключення до бази даних
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// створення об’єкта Statement
S=connect.createStatement();
// цикл виконання запитів SQL, введених з клавіатури
System.out.print("Requête : ");
select=in.readLine();
while(!select.equals("fin")){
// виконання запиту
RS=S.executeQuery(select);
// кількість стовпців
nbColonnes=RS.getMetaData().getColumnCount();
// обробка таблиці результатів
System.out.println("Résultats obtenus\n\n");
while(RS.next()){ // поки є рядок для обробки
// вона відображається на екрані
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// наступний рядок
// наступний запит
System.out.print("Requête : ");
select=in.readLine();
}// поки
} catch (Exception e){
erreur("Erreur " + e,2);
}
// закриття бази даних та потоку вхідних даних
try{
connect.close();
System.out.println("Base " + DB + " fermée");
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// клас
Ось кілька отриманих результатів:
Connexion avec la base ARTICLES établie
Requête : select * from articles order by prix desc
Résultats obtenus
g457,panthère,800000,1,1
z400,léopard,500000,1,1
f807,cachalot,200000,0,0
d600,arc,5000,10,2
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
d800,canoé,1502,12,6
a300,vélo,1202,30,2
f450,essai3,3,3,3
Requête : select nom, prix from articles where prix >10000 order by prix desc
Résultats obtenus
panthère,800000
léopard,500000
cachalot,200000
6.2.3.7. Відправити запит на оновлення бази даних
Об’єкт типу Statement дозволяє зберігати запити SQL. Метод, який цей об’єкт використовує для надсилання запитів SQL на оновлення (INSERT, UPDATE, DELETE) — це вже не метод executeQuery, розглянутий раніше, а метод executeUpdate:
Різниця полягає в результаті: тоді як executeQuery повертав таблицю результатів (ResultSet), executeUpdate повертає кількість рядків, на які вплинула операція оновлення.
6.2.3.8. Третій приклад
Повернемося до попередньої програми, яку трохи змінимо: запити, введені з клавіатури, тепер є запитами на оновлення бази даних ARTICLES.
import java.sql.*;
import java.io.*;
// виводить вміст системної бази даних ARTICLES
public class sql2{
static final String DB="ARTICLES"; // база даних для обробки
public static void main(String arg[]){
Connection connect=null; // підключення до бази
Statement S=null; // об’єкт для надсилання запитів
ResultSet RS=null; // таблиця результатів запиту
String sqlUpdate; // текст запиту SQL на оновлення
int nbLignes; // кількість рядків, на які впливає оновлення
// створення потоку введення з клавіатури
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// підключення до бази даних
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// створення об’єкта Statement
S=connect.createStatement();
// цикл виконання запитів SQL, введених з клавіатури
System.out.print("Requête : ");
sqlUpdate=in.readLine();
while(!sqlUpdate.equals("fin")){
// виконання запиту
nbLignes=S.executeUpdate(sqlUpdate);
// відстеження
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
// наступний запит
System.out.print("Requête : ");
sqlUpdate=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// закриття бази даних та потоку введення
try{
// звільнення ресурсів, пов’язаних із базою даних
RS.close();
S.close();
connect.close();
System.out.println("Base " + DB + " fermée");
// закриття потоку клавіатури
in.close();
} catch (Exception e){}
}// рука
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// клас
Ось результат різних запусків програм sql1 та sql2:
Список рядків бази даних ARTICLES:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,30
arc,10
canoé,12
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
Змінюємо деякі рядки:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : update articles set stock_actu=stock_actu+1 where stock_actu>10
2 ligne(s) ont été mises à jour
Перевірка:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
Додаємо рядок:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',200,20,10)
1 ligne(s) ont été mises à jour
Перевірка:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requ_te : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
nouveau,20
Видаляємо рядок:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : delete from articles where code='x400'
1 ligne(s) ont été mises à jour
Requête : fin
Перевірка:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
cano_,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
6.2.3.9. Відправити довільний запит SQL
Об’єкт Statement, необхідний для відправлення запитів SQL, має метод execute, здатний виконувати будь-який тип запиту SQL:
Результатом є логічне значення true, якщо запит повернув ResultSet (executeQuery), або false, якщо він повернув число (executeUpdate). Отримане значення ResultSet можна отримати за допомогою методу getResultSet, а кількість оновлених рядків — за допомогою методу getUpdateCount. Отже, ми напишемо:
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// виконання запиту SQL
if (S.execute(requête)){
// отримано набір результатів
RS=S.getResultSet();
// обробка ResultSet
...
} else {
// це був запит на оновлення
nbLignes=S.getUpdateCount();
...
}
6.2.3.10. Четвертий приклад
Ми використовуємо принцип роботи програм sql1 та sql2 у програмі sql3, яка тепер здатна виконувати будь-який запит SQL, введений з клавіатури. Щоб зробити програму більш універсальною, характеристики бази даних, з якою потрібно працювати, передаються програмі як параметри.
import java.sql.*;
import java.io.*;
// виклик: pg PILOTE URL UID MDP
// підключається до бази даних URL за допомогою класу JDBC PILOTE
// користувач UID ідентифікується за допомогою пароля MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// перевірка кількості аргументів
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// ініціалізація параметрів з'єднання
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
// інші дані
Statement S=null; // об'єкт відправлення запитів
ResultSet RS=null; // таблиця результатів запиту
String sqlText; // Текст запиту SQL, що має бути виконаний
int nbLignes; // кількість рядків, на які впливає оновлення
int nbColonnes; // кількість стовпців у ResultSet
// створення потоку введення з клавіатури
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// підключення до бази даних
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
// створення об’єкта Statement
S=connect.createStatement();
// цикл виконання запитів SQL, введених з клавіатури
System.out.print("Requête : ");
sqlText=in.readLine();
while(!sqlText.equals("fin")){
// виконання запиту
try{
if(S.execute(sqlText)){
// отримано ResultSet — обробляємо його
RS=S.getResultSet();
// кількість стовпців
nbColonnes=RS.getMetaData().getColumnCount();
// обробка таблиці результатів
System.out.println("\nRésultats obtenus\n-----------------\n");
while(RS.next()){ // доки є рядок для обробки
// виводиться на екран
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// наступний рядок з ResultSet
} else {
// це був запит на оновлення
nbLignes=S.getUpdateCount();
// подальші дії
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//if
} catch (Exception e){
System.out.println("Erreur " +e);
}
// наступний запит
System.out.print("\nNouvelle Requête : ");
sqlText=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// закриття бази даних та потоку вхідних даних
try{
// звільнення ресурсів, пов'язаних з базою даних
RS.close();
S.close();
connect.close();
System.out.println("Base " + arg[1] + " fermée");
// закриття потоку клавіатури
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// клас
Створюємо такий файл запитів:
select * from articles
update articles set stock_mini=stock_mini+5 where stock_mini<5
select nom,stock_mini from articles
insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10)
select * from articles
delete from articles where code='x400'
select * from articles
fin
Програму запускають наступним чином:
Отже, програма зчитує вхідні дані з файлу requetes і записує вихідні дані у файл results. Отримано такі результати:
Connexion avec la base jdbc:odbc:articles établie
Requête : (requete 1 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,3
d600,arc,5000,10,3
d800,canoé,1502,13,7
x123,fusil,3000,10,3
s345,skis nautiques,1800,3,3
f450,essai3,3,3,4
f807,cachalot,200000,0,1
z400,léopard,500000,1,2
g457,panthère,800000,1,2
Nouvelle Requête : (requete 2 du fichier des requetes : update articles set stock_mini=stock_mini+5 where stock_mini<5)
8 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS із базою даних
Останнього разу, коли ми розглядали проблему розрахунку податків, ми використовували графічний інтерфейс, а дані зберігалися у файлі. Ми повертаємося до цієї версії, припускаючи, що тепер дані знаходяться в базі даних ODBC-MySQL. MySQL — це SGBD, що є у відкритому доступі та може використовуватися на різних платформах, зокрема Windows та Linux. За допомогою цього SGBD було створено базу даних dbimpots, що містить єдину таблицю з назвою impots. Доступ до бази даних контролюється за допомогою логіна та пароля, в даному випадку — admimpots/mdpimpots. На знімку екрана показано, як використовувати базу даних dbimpots разом із MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
Графічний інтерфейс програми виглядає наступним чином:

Графічний інтерфейс зазнав деяких змін:
№ | тип | назва | роль |
1 | JTextField | txtConnexion | рядок підключення до бази даних ODBC |
2 | JScrollPane | JScrollPane1 | контейнер для Textarea 3 |
3 | JTextArea | txtStatus | відображає повідомлення про стан, зокрема повідомлення про помилки |
Рядок для входу, введений у полі (1), має такий вигляд: DSN;login;пароль, де
назва DSN джерела даних ODBC | |
| ідентифікатор користувача, який має право на читання бази даних |
| його пароль |
База даних dbimpots була створена вручну за допомогою MySQL. Її перетворюють на джерело даних ODBC наступним чином:
- запускаємо 32-бітний менеджер джерел даних ODBC

- використовуємо кнопку [Add], щоб додати нове джерело даних ODBC

- вибираємо драйвер MySQL і виконуємо [Terminer]

- драйвер MySQL вимагає певної інформації:
1 | ім’я DSN, яке слід присвоїти джерелу даних ODBC — може бути будь-яким |
2 | комп'ютер, на якому виконується SGBD MySQL — у даному випадку localhost. Цікаво зазначити, що база даних може бути віддаленою. Локальні додатки, що використовують джерело даних ODBC, цього навіть не помітять. Це стосується, зокрема, нашого Java-додатка. |
3 | база даних MySQL, яку слід використовувати. MySQL — це SGBD, що керує реляційними базами даних, які є наборами таблиць, пов’язаних між собою відношеннями. Тут вказується назва бази даних, що керується. |
4 | ім’я користувача, який має право доступу до цієї бази даних |
5 | його пароль |
Після визначення джерела даних ODBC можна протестувати нашу програму:
![]() | ![]() |
Розглянемо код, який, порівняно з графічною версією без бази даних, було змінено. Нагадаємо код класу impots, який використовувався до цього:
// створення класу impots
public class impots{
// дані, необхідні для розрахунку податку
// походять із зовнішнього джерела
private double[] limites, coeffR, coeffN;
// конструктор
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// перевіряється, чи всі 3 масиви мають однаковий розмір
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// все гаразд
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//виробник
// розрахунок податку
public long calculer(boolean marié, int nbEnfants, int salaire){
// розрахунок кількості часток
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// розрахунок оподатковуваного доходу та сімейного коефіцієнта
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// розрахунок податку
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// повернення результату
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//розрахувати
}//клас
Цей клас створює три масиви limites, coeffR, coeffN на основі трьох масивів, переданих у якості параметрів його конструктору. Вирішено додати до нього новий конструктор, що дозволяє створювати ті самі три масиви на основі даних з бази даних:
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: ім’я DSN бази даних
// userIMPOTS, mdpIMPOTS: логін/пароль для доступу до бази даних
Для прикладу ми вирішили реалізувати цей новий конструктор не в класі impots, а в похідному класі impotsJDBC:
// імпортовані пакети
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// додано конструктор, що дозволяє створювати
// таблиці limit, coeffr, coeffn на основі таблиці
// податків з бази даних
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: назва DSN бази даних
// userIMPOTS, mdpIMPOTS: логін/пароль для доступу до бази даних
// таблиці даних
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// підключення до бази даних
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// створення об’єкта Statement
Statement S=connect.createStatement();
// запит select
String select="select limites, coeffr, coeffn from impots";
// виконання запиту
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// обробка поточного рядка
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// наступний рядок
// закриття ресурсів
RS.close();
S.close();
connect.close();
// передача даних у обмежені масиви
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//конструктор
}//клас
Конструктор зчитує вміст таблиці impots з бази даних, яка була передана йому як параметр, і заповнює три масиви limites, coeffR, coeffN. Може виникнути певна кількість помилок. Конструктор не обробляє їх, а «передає» їх програмі, що його викликає:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Якщо звернути увагу на наведений вище код, можна побачити, що клас impotsJDBC безпосередньо використовує поля limites, coeffR, coeffN свого базового класу impots. Ці поля оголошені як приватні:
клас impotsJDBC не має прямого доступу до цих полів. Тому ми вносимо першу зміну до базового класу, написавши:
Атрибут protected дозволяє класам, похідним від класу impots, мати прямий доступ до полів, оголошених за допомогою цього атрибута. Нам потрібно внести другу зміну. Конструктор дочірнього класу impotsJDBC оголошується наступним чином:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Ми знаємо, що перед створенням об’єкта дочірнього класу спочатку потрібно створити об’єкт батьківського класу. Для цього конструктор дочірнього класу повинен явно викликати конструктор батьківського класу за допомогою оператора super(....). Тут цього не зроблено, оскільки не зрозуміло, який саме конструктор батьківського класу можна викликати. Наразі існує лише один, і він не підходить. Тоді компілятор шукатиме в батьківському класі конструктор без параметрів, який можна було б викликати. Він його не знаходить, і це призводить до помилки під час компіляції. Тому ми додаємо конструктор без аргументів до нашого класу impots:
Ми оголошуємо його як «protected», щоб ним могли користуватися лише дочірні класи. Скелет класу impots тепер має такий вигляд:
public class impots{
// дані, необхідні для розрахунку податку
// походять із зовнішнього джерела
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// виробник (поле порожнє)
protected impots(){}
// виробник
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//виробник
// розрахунок податку
public long calculer(boolean marié, int nbEnfants, int salaire){
.............
}//розрахувати
}//клас
Дія меню Initialiser у нашому додатку виглядає наступним чином:
void mnuInitialiser_actionPerformed(ActionEvent e) {
// отримуємо рядок підключення
Pattern séparateur=Pattern.compile("\\s*;\\s*");
String[] champs=séparateur.split(txtConnexion.getText().trim());
// потрібні три поля
if(champs.length!=3){
// помилка
txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
// повернення до графічного інтерфейсу
txtConnexion.requestFocus();
return;
}//if
// завантажуються дані
try{
// створення об’єкта impotsJDBC
objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
// підтвердження
txtStatus.setText("Données chargées");
// зарплату можна змінити
txtSalaire.setEditable(true);
// більше змін неможливо
mnuInitialiser.setEnabled(false);
txtConnexion.setEditable(false);
}catch(Exception ex){
// проблема
txtStatus.setText("Erreur : " + ex.getMessage());
// кінець
return;
}//перехоплення
}
Після створення об’єкта objImpots програма працює так само, як і вже написана графічна програма. Читачеві пропонується звернутися до неї.
6.4. Вправи
6.4.1. Вправа 1
Створити графічний інтерфейс для попередньої програми sql3.
6.4.2. Вправа 2
Java-аплет може отримати доступ до бази даних лише через сервер, з якого він був завантажений. Оскільки аплет не має доступу до диска комп’ютера, на якому він виконується, база даних не може знаходитися на комп’ютері клієнта, який використовує цей аплет. Отже, маємо таку ситуацію:

Комп’ютер, на якому виконується аплет, сервер та комп’ютер, на якому розміщена база даних, можуть бути трьома різними комп’ютерами. Тут припускаємо, що база даних знаходиться на сервері.
Завдання 1
Напишіть на Java наступний серверний додаток:
- серверний додаток працює на порту, який передається йому як параметр
- коли клієнт підключається, серверний додаток надсилає повідомлення
- потім клієнт надсилає параметри, необхідні для підключення до бази даних, та запит, який він хоче виконати, у такому вигляді:
Параметри розділяються косою рискою. Перші чотири параметри належать до програми sql3, описаної в цьому розділі.
- Потім серверний додаток встановлює з’єднання із вказаною базою даних, яка має знаходитися на тому самому комп’ютері, що й сам додаток, і виконує над нею запит SQL. Результати повертаються клієнту у такому форматі:
...
якщо це результат запиту Select, або
для повернення кількості рядків, на які вплинув запит на оновлення. Якщо трапляється помилка підключення до бази даних або виконання запиту, додаток повертає
- після виконання запиту серверна програма закриває з’єднання.
Завдання 2
Створіть Java-аплет, що дозволяє надсилати запити до зазначеного вище сервера. Можна скористатися графічним інтерфейсом із попереднього завдання 1. Оскільки порт сервера може змінюватися, його потрібно буде ввести в інтерфейсі аплету. Те саме стосується всіх параметрів, необхідних для надсилання рядка:
, яку клієнт повинен надіслати на сервер.
6.4.3. Вправа 3
У наведеному нижче тексті описано задачу, яку спочатку планувалося вирішувати у Visual Basic. Адаптуйте її для обробки на Java в аплеті. Цей аплет використовуватиме сервер із вправи 2. Графічний інтерфейс можна змінити з урахуванням нового контексту виконання.
Пропонується створити додаток, що демонструє різні можливі операції оновлення таблиці бази даних ACCESS. База даних ACCESS називається articles.mdb. Вона містить єдину таблицю з назвою «articles», у якій перелічено товари, що продаються компанією. Її структура така:
назва | тип |
code | 4-символьний код товару |
nom | назва (рядок символів) |
prix | ціна (фактична) |
stock_actu | його поточний запас (ціле число) |
stock_mini | мінімальний запас (ціле число), при недосягненні якого необхідно поповнити товар |
Ми пропонуємо переглядати та оновлювати цю таблицю за допомогою наступної форми:

Елементи управління цієї форми такі:
№ | тип | назва | Функція |
1 | текстове поле | картка | номер переглянутої картки параметр enabled має значення false |
2 | текстове поле | код | код товару |
3 | текстове поле | назва | назва товару |
4 | текстове поле | ціна | ціна товару |
5 | текстове поле | поточний | поточний запас товару |
6 | текстове поле | мінімальний | мінімальний запас товару |
7 | data | data1 | Перевірка даних, пов'язаних з базою даних databasename=шлях до файлу articles.mdb recordsource=articles connect=access |
8 | HScrollBar | position | дозволяє пересуватися по таблиці |
9 | кнопка | OK | дозволяє підтвердити оновлення — з’являється лише під час оновлення |
10 | кнопка | Скасувати | дозволяє скасувати оновлення — з’являється лише під час оновлення |
11 | кадр | кадр1 | для краси |
12 | текстове поле | базове ім'я | ім'я відкритої бази даних параметр enabled має значення false |
13 | textbox | sourcename | назва відкритої таблиці параметр enabled має значення false |
перевірка | Особливості |
data1 | Поля databasename та recordsource заповнені. У полі databasename слід вказати базу даних Access articles.mdb з вашого каталогу, а в полі recordsource — таблицю «articles». |
code | Це текстове поле, яке потрібно пов’язати з полем «code» поточного запису з data1. Для цього заповнюємо два поля: datasource: вводимо data1, щоб вказати, що текстове поле пов’язане з таблицею, асоційованою з data1 datafield: вибираємо поле «code» з таблиці «articles» Після цих операцій текстове поле «code» завжди міститиме значення поля «code» поточного запису з data1. І навпаки, зміна вмісту цього текстового поля призведе до зміни значення поля «code» у поточній картці. Те саме робимо для інших текстових полів |
nom | datasource: data1 datafield: nom |
prix | джерело даних: data1 поле даних: ціна |
actuel | джерело даних: data1 поле даних: stock_actu |
minimum | джерело даних: data1 поле даних: stock_mini |
Структура меню така
Редагування | Огляд | Вихід |
Додати | Назад | |
Редагувати | Наступний | |
Видалити | Перший | |
Останній |
Роль різних опцій така:
меню | name | функція |
mnuajouter | для додавання нового запису до таблиці товарів | |
mnusupprimer | для видалення з таблиці товарів запису, який зараз переглядається | |
mnumodifier | щоб змінити у таблиці товарів запис, який зараз переглядається | |
mnuprecedent | для переходу до попереднього запису | |
mnusuivant | щоб перейти до наступного запису | |
попередній | щоб перейти до першого запису | |
mnudernier | для переходу до останнього запису | |
mnuquitter | щоб вийти з програми |
Під час події form_load відкривається таблиця, пов’язана з data1 (data1.refresh). Якщо відкриття не вдається, відображається повідомлення про помилку, і програма завершується (end). В іншому випадку відображається форма з першим записом таблиці articles. Введення даних у поля неможливе (властивість enabled має значення false). Введення даних можливе лише за допомогою опцій «Додати» та «Редагувати». Кнопки OK та «Скасувати» приховані (visible=false).
Ми пропонуємо створити процедури, пов’язані з різними опціями меню, а також з кнопками OK та «Скасувати». На першому етапі ми проігноруємо такі моменти:
- дозвіл/блокування певних опцій меню: наприклад, опція «Далі» має бути заблокована, якщо курсор знаходиться на останньому записі таблиці
- управління горизонтальним прокручуванням
меню «Перегляд»/«Далі»
- переходить до наступного запису (data1.RecordSet.MoveNext), якщо курсор не знаходиться в кінці файлу (data1.RecordSet.EOF). Оновлює текстове поле запису (data1.recordset.absoluteposition/ data1.recordset.recordcount).
меню «Огляд»/«Попередній»
- переходить до попереднього запису (data1.RecordSet.MovePrevious), якщо не перебуваєте на початку файлу (data1.RecordSet.BOF). Оновлює текстове поле картки.
меню «Перегляд»/«Перший»
- переходить до першого запису (data1.RecordSet.MoveFirst), якщо файл не порожній (data1.recordset.recordcount=0). Оновлює текстове поле картки.
меню «Перегляд»/«Останній»
- переходить до останнього запису (data1.RecordSet.MoveLast), якщо файл не порожній (data1.recordset.recordcount=0). Оновлює текстове поле картки.
меню «Редагування»/«Додати»
- дозволяє додати запис до таблиці
- переводить у режим додавання запису (data1.recordset.addnew)
- дозволяє вводити дані у 5 полів: код, назва, ціна тощо (enabled=true)
- блокує меню «Редагування», «Огляд», «Вихід» (enabled=false)
- відображає кнопки OK та «Скасувати» (visible=true)
кнопка OK
- підтверджує зміну запису (data1.recordset.Update)
- приховує кнопки OK та «Скасувати» (visible=false)
- увімкнути опції «Редагувати», «Огляд», «Вийти» (enabled=true)
- оновлює текстове поле запису
кнопка «Скасувати»
- скасовує зміну запису (data1.recordset.CancelUpdate)
- приховує кнопки OK та «Скасувати» (visible=false)
- увімкнути опції «Редагувати», «Огляд», «Вийти» (enabled=true)
- оновлює текстове поле запису
меню «Редагування»/«Змінити»
- дозволяє редагувати запис, що відображається у формі
- переводить у режим редагування запису (data1.recordset.edit)
- дозволяє вводити дані у 4 поля: «назва», «ціна» тощо (enabled=true), але не у поле «код» (enabled=false)
- приховує меню «Редагування», «Огляд», «Вихід» (enabled=false)
- відображає кнопки «OK» та «Скасувати» (visible=true)
меню «Редагування»/«Видалити»
- дозволяє видалити (data1.recordset.delete) відображений запис із таблиці
- переходить до наступного запису (data1.recordset.movenext)
меню «Вийти»
- завантажує аркуш (unload me)
подія form_unload (cancel as integer)
- активується операцією unload me або закриттям форми за допомогою Alt-F4 чи подвійним клацанням на системному полі, тобто не обов’язково за допомогою опції «Вийти».
- виводить запит «Чи дійсно ви хочете вийти з програми?» з двома кнопками «Так»/«Ні» (msgbox зі стилем=vbyes+vbno)
- якщо відповідь «Ні» (=vbno), то variable «cancel» встановлюється на -1 і процедура form_unload завершується. Значення «cancel» -1 означає, що закриття вікна відхилено.
- якщо відповідь «Так» (=vbyes), база даних закривається (data1.recordset.close, data1.database.close).
Тут ми розглядаємо питання дозволу/блокування меню. Після кожної операції, що змінює поточну картку, викликається процедура, яку можна назвати Oueston. Вона перевіряє наступні умови:
. якщо файл порожній,
- блокуються меню «Огляд», «Редагування/Видалення»,
- а інші — дозволимо
. якщо поточний запис є першим,
- заблокують «Огляд/Попередній»
- дозволено решту
. якщо поточна картка є останньою, то
- заблокуємо «Перегляд/Наступний»
- дозволити решту
Горизонтальний регулятор має три важливі поля:
- min: його мінімальне значення
- max: його максимальне значення
- value: його поточне значення
Ініціалізація приводу
Під час завантаження (form_load) привід буде ініціалізовано таким чином:
- min=0
- max=data1.recordset.recordcount-1
- value=1
Слід зауважити, що одразу після відкриття бази даних (data1.refresh) кількість записів у таблиці, що позначається як data1.recordset.recordcount, є неправильною. Щоб воно стало правильним, потрібно перейти до кінця таблиці (MoveLast), а потім повернутися до початку таблиці (MoveFirst).
Пряме керування приводом
Позиція курсора регулятора відповідає позиції в таблиці.
Коли користувач змінює положення регулятора (яке тут називається «position»), спрацьовує подія position_change. У цій події ми змінимо поточний запис таблиці, щоб він відображав зміну, виконану на регуляторі. Для цього ми використаємо поле absoluteposition з data1.recordset. Коли цьому полю присвоюється значення i, запис № i у таблиці стає поточним. Записи нумеруються, починаючи з 0, і тому мають номери в діапазоні [0,data1.recordset.recordcount-1]. У процедурі position_change нам достатньо написати
щоб поточна картка, яка відображається у формі, відображала зміну, виконану на регуляторі.
Після цього слід викликати процедуру Oueston для оновлення меню.
Оновлення регулятора
Оскільки положення курсора регулятора має відповідати позиції в таблиці, значення регулятора потрібно оновлювати щоразу, коли в таблиці відбувається зміна поточної картки, спричинена тим чи іншим меню. Оскільки кожне з цих меню викликає процедуру Oueston, найкраще розмістити це оновлення також у цій процедурі. Достатньо написати тут:
Додаємо опцію «Перегляд/Пошук», яка дозволяє користувачеві переглянути товар за вказаним кодом.
Коли ця опція активована, відбуваються такі події:
- переходимо в режим «Додати запис» (Addnew) з єдиною метою — не змінювати поточний запис, на якому ми перебували під час увімкнення опції;
- дозволяється введення даних у поле «Код» та очищується вміст поля «Картка»,
- блокуються меню та відображаються кнопки «OK» і «Скасувати»;
- коли користувач натискає OK, нам потрібно знайти запис, що відповідає коду, введеному користувачем. Однак процедура OK_click вже використовується для опцій «Огляд/Додати» та «Огляд/Редагувати». Щоб розрізнити ці випадки, нам доведеться керувати глобальною змінною, яку ми тут назвемо «стан» і яка матиме три можливі значення: «додати», «редагувати» та «шукати». Процедури, пов’язані з кнопками OK та «Скасувати», використовуватимуть цю змінну, щоб визначити, у якому контексті вони викликаються.
- Якщо стан дорівнює «шукати», у процедурі, пов’язаній із OK,
- скасовується операція addnew (data1.recordset.cancelupdate), оскільки додавання запису не планувалося. Слід зазначити, що в цьому випадку поточний запис знову стає тим, що був на екрані до операції «Огляд/Пошук».
- Створюється критерій пошуку, пов’язаний із кодом, і запускається пошук (data1.recordset.findfirst критерій),
- якщо пошук не вдався (data1.recordset.nomatch=true), про це повідомляється користувачеві, після чого система повертається в режим додавання (addnew) і виходить із процедури OK. Користувач повинен буде ввести новий код або вибрати опцію «Скасувати».
- якщо пошук завершився успішно, знайдена картка стає новою поточною карткою. Відновлюються меню, приховуються кнопки OK/«Скасувати», блокується введення даних у поле «код» і процедура завершується.
- . Якщо стан дорівнює «шукати», у процедурі, пов’язаній із «Скасувати»,
- скасовується операція addnew (data1.recordset.cancelupdate). Після цього система автоматично повернеться до поточної запису, що була до операції «Перегляд/Пошук».
- відновлює меню, приховує кнопки OK/Скасувати, блокує введення даних у полі коду та завершує процедуру.
Товар повинен мати унікальний код. Зробіть так, щоб в опції «Огляд/Додати» додавання було відхилено, якщо запис, який потрібно додати, має код товару, що вже існує в таблиці.
6.4.4. Вправа 4
Тут представлено веб-додаток, що базується на сервері з вправи 2. Це зачаток додатка для електронної комерції.
Клієнт замовляє товари за допомогою такого веб-інтерфейсу:

Він може виконувати такі операції:
- вибирає товар із випадаючого списку
- вказує бажану кількість
- підтверджує покупку за допомогою кнопки Acheter
- його покупка відображається у списку придбаних товарів
- він може видалити товари з цього списку, вибравши товар і натиснувши кнопку Retirer
- коли він натискає кнопку Bilan, він отримує такий підсумок:

Звіт дозволяє користувачеві ознайомитися з деталями свого рахунку. Користувач має доступ до детальної інформації щодо товару, обраного у випадаючому списку за допомогою кнопки Informations:

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

Перед збереженням замовлення додаток запитує підтвердження:

Після підтвердження замовлення додаток обробляє його та відображає сторінку підтвердження:

Насправді додаток не обробляє замовлення. Він лише надсилає користувачеві електронний лист із проханням оплатити суму покупок:
Cher client,
Vous trouverez ci-dessous le détail de votre commande au magasin SuperPrix. Elle vous sera livrée après réception de votre chèque établi à l'ordre de SuperPrix et à envoyer à l'adresse suivante :
SuperPrix
ISTIA
62 av Notre-Dame du Lac
49000 Angers
France
Nous vous remercions vivement de votre commande
----------------------------------------
Votre commande
----------------------------------------
article, quantité, prix unitaire, total
========================================
vélo, 2, 1202.00 F, 2404.00 F
skis nautiques, 3, 1800.00 F, 5400.00 F
Total à payer : 7804 F
Завдання: Створити еквівалент цього веб-додатка за допомогою Java-аплету.

