Skip to content

6. Управління базами даних за допомогою API JDBC

6.1. Загальні відомості

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

Image

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

Image

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

Image

6.2. Важливі етапи роботи з базами даних

6.2.1. Вступ

У додатку JAVA, що використовує базу даних з інтерфейсом JDBC, зазвичай виконуються такі етапи:

  1. Підключення до бази даних
  2. Відправлення запитів SQL до бази даних
  3. Отримання та обробка результатів цих запитів
  4. Закриття з’єднання

Етапи 2 і 3 виконуються повторно, а закриття з’єднання відбувається лише після завершення роботи з базою даних. Це відносно класична схема, до якої ви, можливо, вже звикли, якщо раніше працювали з базою даних в інтерактивному режимі. Ми детально розглянемо кожен із цих етапів на прикладі. Розглянемо базу даних ACCESS, яка називається ARTICLES і має таку структуру:

ім’я
тип
code
4-символьний код товару
nom
назва (рядок символів)
prix
ціна (фактична)
stock_actu
його поточний запас (ціле число)
stock_mini
мінімальний запас (ціле число), при недосягненні якого необхідно поповнити товар

Ця база даних ACCESS визначена як джерело даних «користувача» у менеджері баз даних ODBC:

Image

Image

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

Image

Ця конфігурація полягає, по суті, у прив’язці до бази даних ARTICLES файлу Access articles.mdb, що відповідає цій базі. Після цього база даних ARTICLES стає доступною для додатків, що використовують інтерфейс ODBC.

6.2.2. Етап підключення

Щоб працювати з базою даних, Java-додаток спочатку має виконати етап підключення. Це здійснюється за допомогою методу наступного класу:

Connection DriverManager.getConnection(String URL, String id, String mdp)

з

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.forName(String nomClasse)

Клас 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.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 requete=connexion.CreateStatement();

Отримавши об’єкт Statement, можна відправляти запити SQL. Це відбуватиметься по-різному залежно від того, чи є запит запитом на отримання даних чи на оновлення бази.

6.2.3.2. Відправлення запиту на отримання даних з бази

Запит на отримання даних зазвичай має такий вигляд:

    select col1, col2,... from table1, table2,...
    where condition
    order by expression
    ...

Обов’язковими є лише ключові слова першого рядка, решта — необов’язкові. Існують також інші ключові слова, які тут не наведено.

  1. Здійснюється з'єднання з усіма таблицями, що знаходяться після ключового слова from
  2. Зберігаються лише стовпці, що знаходяться після ключового слова `select`
  3. Зберігаються лише ті рядки, які відповідають умові ключового слова `where`
  4. Отримані рядки, впорядковані відповідно до виразу ключового слова `order by`, утворюють результат запиту.

Результатом запиту select є таблиця. Якщо розглянути попередню таблицю ARTICLES і отримати назви товарів, поточний запас яких нижчий за мінімальний поріг, слід написати: *select nom from articles where stock\_actu&lt;stock\_mini*. Якщо потрібно отримати їх у алфавітному порядку за назвами, слід написати: select name from articles where stock_actu<stock_mini order by name

Для виконання такого запиту клас Statement надає метод executeQuery:

    ResultSet executeQuery(String requête)

де 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:

    boolean next()

Цей метод намагається перейти до наступного рядка ResultSet і повертає true у разі успіху, а false — у разі невдачі. У разі успіху наступний рядок стає новим поточним рядком. Попередній рядок втрачається, і повернутися назад, щоб його відновити, неможливо. Таблиця ResultSet має стовпці col1, col2,.... Для роботи з різними полями поточного рядка доступні такі методи:

Type getType("coli") 

для отримання поля «coli» з поточного рядка. Type позначає тип поля coli. Досить часто метод getString застосовують до всіх полів, що дозволяє отримати вміст поля у вигляді символьного рядка. Потім, за необхідності, його конвертують. Якщо назва стовпця невідома, можна скористатися методами

Type getType(i) 

, де 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 отримується за допомогою:

    RS.getMetaData()

Звернемо увагу на два корисні методи в класі ResultSetMetaData:

  1. int getColumnCount(), що повертає кількість стовпців у ResultSet
  2. 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:

    int executeUpdate(String requête)

Різниця полягає в результаті: тоді як 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:

    boolean execute(String requête)

Результатом є логічне значення 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

Програму запускають наступним чином:

E:\data\java\jdbc\0>java sql3 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles <requetes >results

Отже, програма зчитує вхідні дані з файлу 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 é 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 é 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 é 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

Графічний інтерфейс програми виглядає наступним чином:

Image

Графічний інтерфейс зазнав деяких змін:

тип
назва
роль
1
JTextField
txtConnexion
рядок підключення до бази даних ODBC
2
JScrollPane
JScrollPane1
контейнер для Textarea 3
3
JTextArea
txtStatus
відображає повідомлення про стан, зокрема повідомлення про помилки

Рядок для входу, введений у полі (1), має такий вигляд: DSN;login;пароль, де

DSN
назва DSN джерела даних ODBC

login
ідентифікатор користувача, який має право на читання бази даних

motdepasse
його пароль

База даних dbimpots була створена вручну за допомогою MySQL. Її перетворюють на джерело даних ODBC наступним чином:

  • запускаємо 32-бітний менеджер джерел даних ODBC

Image

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

Image

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

Image

  • драйвер 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. Ці поля оголошені як приватні:

    private double[] limites, coeffR, coeffN;

клас impotsJDBC не має прямого доступу до цих полів. Тому ми вносимо першу зміну до базового класу, написавши:

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

Атрибут protected дозволяє класам, похідним від класу impots, мати прямий доступ до полів, оголошених за допомогою цього атрибута. Нам потрібно внести другу зміну. Конструктор дочірнього класу impotsJDBC оголошується наступним чином:

  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

Ми знаємо, що перед створенням об’єкта дочірнього класу спочатку потрібно створити об’єкт батьківського класу. Для цього конструктор дочірнього класу повинен явно викликати конструктор батьківського класу за допомогою оператора super(....). Тут цього не зроблено, оскільки не зрозуміло, який саме конструктор батьківського класу можна викликати. Наразі існує лише один, і він не підходить. Тоді компілятор шукатиме в батьківському класі конструктор без параметрів, який можна було б викликати. Він його не знаходить, і це призводить до помилки під час компіляції. Тому ми додаємо конструктор без аргументів до нашого класу impots:

   // порожній конструктор
  protected 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-аплет може отримати доступ до бази даних лише через сервер, з якого він був завантажений. Оскільки аплет не має доступу до диска комп’ютера, на якому він виконується, база даних не може знаходитися на комп’ютері клієнта, який використовує цей аплет. Отже, маємо таку ситуацію:

Image

Комп’ютер, на якому виконується аплет, сервер та комп’ютер, на якому розміщена база даних, можуть бути трьома різними комп’ютерами. Тут припускаємо, що база даних знаходиться на сервері.

Завдання 1

Напишіть на Java наступний серверний додаток:

  • серверний додаток працює на порту, який передається йому як параметр
  • коли клієнт підключається, серверний додаток надсилає повідомлення
200 - Bienvenue - Envoyez votre requête
  • потім клієнт надсилає параметри, необхідні для підключення до бази даних, та запит, який він хоче виконати, у такому вигляді:
Pilote Java/URL base/UID/MDP/requête SQL

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

  • Потім серверний додаток встановлює з’єднання із вказаною базою даних, яка має знаходитися на тому самому комп’ютері, що й сам додаток, і виконує над нею запит SQL. Результати повертаються клієнту у такому форматі:
100 - ligne1
100 - ligne2

...

якщо це результат запиту Select, або

101 - nbLignes

для повернення кількості рядків, на які вплинув запит на оновлення. Якщо трапляється помилка підключення до бази даних або виконання запиту, додаток повертає

500 - Message d’erreur
  • після виконання запиту серверна програма закриває з’єднання.

Завдання 2

Створіть Java-аплет, що дозволяє надсилати запити до зазначеного вище сервера. Можна скористатися графічним інтерфейсом із попереднього завдання 1. Оскільки порт сервера може змінюватися, його потрібно буде ввести в інтерфейсі аплету. Те саме стосується всіх параметрів, необхідних для надсилання рядка:

Pilote Java/URL base/UID/MDP/requête SQL

, яку клієнт повинен надіслати на сервер.

6.4.3. Вправа 3

У наведеному нижче тексті описано задачу, яку спочатку планувалося вирішувати у Visual Basic. Адаптуйте її для обробки на Java в аплеті. Цей аплет використовуватиме сервер із вправи 2. Графічний інтерфейс можна змінити з урахуванням нового контексту виконання.

Пропонується створити додаток, що демонструє різні можливі операції оновлення таблиці бази даних ACCESS. База даних ACCESS називається articles.mdb. Вона містить єдину таблицю з назвою «articles», у якій перелічено товари, що продаються компанією. Її структура така:

назва
тип
code
4-символьний код товару
nom
назва (рядок символів)
prix
ціна (фактична)
stock_actu
його поточний запас (ціле число)
stock_mini
мінімальний запас (ціле число), при недосягненні якого необхідно поповнити товар

Ми пропонуємо переглядати та оновлювати цю таблицю за допомогою наступної форми:

Image

Елементи управління цієї форми такі:

тип
назва
Функція
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

Створення аркуша за допомогою VB

перевірка
Особливості
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
функція
Ajouter
mnuajouter
для додавання нового запису до таблиці товарів
Supprimer
mnusupprimer
для видалення з таблиці товарів запису, який зараз переглядається
Modifier
mnumodifier
щоб змінити у таблиці товарів запис, який зараз переглядається
Précédent
mnuprecedent
для переходу до попереднього запису
Suivant
mnusuivant
щоб перейти до наступного запису
Premier
попередній
щоб перейти до першого запису
Dernier
mnudernier
для переходу до останнього запису
Quitter
mnuquitter
щоб вийти з програми

Завантаження аркуша

Під час події form_load відкривається таблиця, пов’язана з data1 (data1.refresh). Якщо відкриття не вдається, відображається повідомлення про помилку, і програма завершується (end). В іншому випадку відображається форма з першим записом таблиці articles. Введення даних у поля неможливе (властивість enabled має значення false). Введення даних можливе лише за допомогою опцій «Додати» та «Редагувати». Кнопки OK та «Скасувати» приховані (visible=false).


Частина 1

Ми пропонуємо створити процедури, пов’язані з різними опціями меню, а також з кнопками 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).

Частина 2 — Управління меню

Тут ми розглядаємо питання дозволу/блокування меню. Після кожної операції, що змінює поточну картку, викликається процедура, яку можна назвати Oueston. Вона перевіряє наступні умови:

. якщо файл порожній,

  • блокуються меню «Огляд», «Редагування/Видалення»,
  • а інші — дозволимо

. якщо поточний запис є першим,

  • заблокують «Огляд/Попередній»
  • дозволено решту

. якщо поточна картка є останньою, то

  • заблокуємо «Перегляд/Наступний»
  • дозволити решту

Частина 3 — Управління горизонтальним перемикачем

Горизонтальний регулятор має три важливі поля:

  • 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 нам достатньо написати

    data1.recordset.absoluteposition=position.value

щоб поточна картка, яка відображається у формі, відображала зміну, виконану на регуляторі.

Після цього слід викликати процедуру Oueston для оновлення меню.

Оновлення регулятора

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

    position.value=data1.recordset.absoluteposition

Частина 4 — Опція «Пошук»

Додаємо опцію «Перегляд/Пошук», яка дозволяє користувачеві переглянути товар за вказаним кодом.

Коли ця опція активована, відбуваються такі події:

  • переходимо в режим «Додати запис» (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/Скасувати, блокує введення даних у полі коду та завершує процедуру.

Частина 5 — Управління кодом

Товар повинен мати унікальний код. Зробіть так, щоб в опції «Огляд/Додати» додавання було відхилено, якщо запис, який потрібно додати, має код товару, що вже існує в таблиці.

6.4.4. Вправа 4

Тут представлено веб-додаток, що базується на сервері з вправи 2. Це зачаток додатка для електронної комерції.

Клієнт замовляє товари за допомогою такого веб-інтерфейсу:

Image

Він може виконувати такі операції:

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

Image

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

Image

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

Image

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

Image

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

Image

Насправді додаток не обробляє замовлення. Він лише надсилає користувачеві електронний лист із проханням оплатити суму покупок:

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-аплету.