Skip to content

6. Zarządzanie bazami danych za pomocą API JDBC

6.1. Informacje ogólne

Na rynku dostępnych jest wiele baz danych. Aby ujednolicić dostęp do baz danych w systemie Windows, firma Microsoft opracowała interfejs o nazwie ODBC (Open DataBase Connectivity). Warstwa ta ukrywa specyfikę poszczególnych baz danych pod standardowym interfejsem. W systemie Windows dostępnych jest wiele sterowników ułatwiających dostęp do baz danych. Oto przykładowa lista sterowników zainstalowanych na komputerze z systemem Win95:

Image

Aplikacja oparta na tych sterownikach może korzystać z dowolnej z powyższych baz danych bez konieczności przepisywania kodu.

Image

Aby aplikacje Java również mogły korzystać z interfejsu ODBC, firma Sun stworzyła interfejs JDBC (Java DataBase Connectivity), który będzie pośredniczył między aplikacją Java a interfejsem ODBC:

Image

6.2. Najważniejsze etapy korzystania z baz danych

6.2.1. Wprowadzenie

W aplikacji JAVA korzystającej z bazy danych za pośrednictwem interfejsu JDBC zazwyczaj występują następujące etapy:

  1. Połączenie z bazą danych
  2. Wysyłanie zapytań SQL do bazy danych
  3. Odbiór i przetwarzanie wyników tych zapytań
  4. Zamknięcie połączenia

Kroki 2 i 3 są wykonywane wielokrotnie, a zamknięcie połączenia następuje dopiero po zakończeniu pracy z bazą danych. Jest to stosunkowo klasyczny schemat, z którym być może są Państwo zaznajomieni, jeśli korzystali Państwo z bazy danych w trybie interaktywnym. Każdy z tych kroków omówimy szczegółowo na przykładzie. Rozważmy bazę danych ACCESS o nazwie ARTICLES, posiadającą następującą strukturę:

nazwa
typ
code
4-znakowy kod artykułu
nom
nazwa (ciąg znaków)
prix
jego cena (rzeczywista)
stock_actu
aktualny stan magazynowy (liczba całkowita)
stock_mini
minimalny stan magazynowy (liczba całkowita), poniżej którego należy uzupełnić zapasy danego artykułu

Ta baza danych ACCESS jest zdefiniowana jako źródło danych „użytkownika” w menedżerze baz danych ODBC:

Image

Image

Jej parametry są określone za pomocą przycisku Configurer w następujący sposób:

Image

Konfiguracja ta polega zasadniczo na powiązaniu bazy ARTICLES z plikiem Access articles.mdb odpowiadającym tej bazie. Po wykonaniu tej czynności baza ARTICLES staje się dostępna dla aplikacji korzystających z interfejsu ODBC.

6.2.2. Etap nawiązywania połączenia

Aby korzystać z bazy danych, aplikacja Java musi najpierw przeprowadzić fazę nawiązywania połączenia. Odbywa się to za pomocą następującej metody klasy:

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

wraz z

DriverManager: klasa Java zawierająca listę sterowników dostępnych dla aplikacji

Connection: klasa Java ustanawiająca połączenie między aplikacją a bazą danych, dzięki któremu aplikacja będzie przesyłać zapytania SQL do bazy danych i odbierać wyniki

URL: nazwa identyfikująca bazę danych. Nazwa ta jest analogiczna do nazw domen internetowych typu URL. Dlatego stanowi część klasy URL. Jednak Internet nie odgrywa tu żadnej roli. URL ma następującą postać:

jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2

W naszych przykładach, w których będziemy korzystać wyłącznie ze sterowników ODBC, sterownik nosi nazwę odbc. Trzecia część adresu URL składa się z nazwy źródła wraz z ewentualnymi parametrami. W naszych przykładach będą to źródła znane systemowi o nazwie ODBC. Zatem URL dla wcześniej zdefiniowanego źródła danych Articles będzie

jdbc:odbc:Artykuły

id: identyfikator użytkownika (login)

mdp: hasło użytkownika (password)

Podsumowując, program łączy się z bazą danych:

  • określoną nazwą (URL)
  • pod identyfikatorem użytkownika (id, hasło)

Jeśli te trzy parametry są poprawne i jeśli istnieje sterownik umożliwiający nawiązanie połączenia aplikacji Java z określoną bazą danych, wówczas nawiązywane jest połączenie między aplikacją Java a bazą danych. Połączenie to jest reprezentowane dla programu przez obiekt typu Connection zwracany przez klasę DriverManager. Ponieważ nawiązanie tego połączenia może się nie powieść z różnych powodów, może ono wywołać wyjątek. Napiszemy zatem:


    Connection connexion=null;
    URL base=...;
    String id=...;
    String mdp=...;
try{
        connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
        // obsługa wyjątku
}

Aby możliwe było połączenie z bazą danych, należy dysponować odpowiednim sterownikiem. W naszych przykładach będzie to sterownik ODBC, obsługujący wymaganą bazę danych. Oprócz tego, że sterownik ten musi znajdować się na liście sterowników ODBC zainstalowanych na komputerze, konieczne jest również posiadanie klasy JAVA, która będzie pełnić rolę interfejsu komunikacyjnego z tym sterownikiem. W tym celu aplikacja zażąda niezbędnej klasy w następujący sposób:

    Class.forName(String nomClasse)

Klasa Class nie jest w żaden sposób powiązana z interfejsem JDBC. Jest to ogólna klasa służąca do zarządzania klasami. Jej metoda statyczna forName umożliwia dynamiczne ładowanie klasy, a tym samym korzystanie z jej atrybutów i metod statycznych. Klasa pełniąca rolę interfejsu dla sterowników ODBC systemu Windows o nazwie MS nosi nazwę „sun.jdbc.odbc.JdbcOdbcDriver”. Należy zatem napisać (metoda może wygenerować wyjątek):

try{
    Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
    // obsługa wyjątku (klasa nie istnieje)
}

Klasy niezbędne dla interfejsu JDBC znajdują się w pakiecie java.sql. Na początku programu należy zatem wpisać:

    import java.sql.*;

Oto program umożliwiający połączenie z bazą danych:

import java.sql.*;
import java.io.*;

// wywołanie: pg PILOTE URL UID MDP
// łączy się z bazą URL za pomocą klasy JDBC PILOTE
// użytkownik UID jest identyfikowany za pomocą hasła MDP

public class connexion1{
    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){
        // sprawdzanie liczby argumentów
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);
        // połączenie z bazą danych
        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);
        }
        // zamknięcie bazy danych
        try{
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
        } catch (Exception e){}
    }// main

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasa    

Oto przykładowe wyniki działania:

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. Wysyłanie zapytań do bazy danych

Interfejs JDBC umożliwia wysyłanie zapytań SQL do bazy danych połączonej z aplikacją Java, a także przetwarzanie wyników tych zapytań. Język SQL (Structured Query Language) jest standardowym językiem zapytań dla relacyjnych baz danych. Wyróżnia się w nim kilka rodzajów zapytań:

  • zapytania o dane z bazy (SELECT)
  • zapytania aktualizujące bazę (INSERT, DELETE, UPDATE)
  • zapytania dotyczące tworzenia/usuwania tabel (CREATE, DELETE)

Zakładamy tutaj, że czytelnik zna podstawy języka SQL.

6.2.3.1. Klasa Statement

Aby wysłać dowolne zapytanie SQL do bazy danych, aplikacja Java musi dysponować obiektem typu Statement. Obiekt ten będzie przechowywał między innymi tekst zapytania. Obiekt ten jest z konieczności powiązany z bieżącym połączeniem. Jest to zatem metoda ustanowionego połączenia, która umożliwia tworzenie obiektów Statement niezbędnych do wysyłania zapytań SQL. Jeśli connexion jest obiektem symbolizującym połączenie z bazą danych, obiekt Statement uzyskuje się w następujący sposób:

    Statement requete=connexion.CreateStatement();

Po uzyskaniu obiektu Statement można wysyłać zapytania SQL. Odbywa się to w różny sposób w zależności od tego, czy zapytanie dotyczy pobierania danych, czy aktualizacji bazy.

6.2.3.2. Wysyłanie zapytania do bazy danych

Zapytanie o dane ma zazwyczaj postać:

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

Obowiązkowe są tylko słowa kluczowe z pierwszego wiersza, pozostałe są opcjonalne. Istnieją również inne słowa kluczowe, które nie zostały tutaj przedstawione.

  1. Tworzy się połączenie ze wszystkimi tabelami, które znajdują się za słowem kluczowym „from”
  2. Zachowane są tylko kolumny występujące po słowie kluczowym „select”
  3. Zachowane są wyłącznie wiersze spełniające warunek określony przez słowo kluczowe `where`
  4. Wynikowe wiersze, uporządkowane zgodnie z wyrażeniem zawartym w słowie kluczowym `order by`, stanowią wynik zapytania.

Wynikiem zapytania select jest tabela. Jeśli weźmiemy pod uwagę poprzednią tabelę ARTICLES i chcemy uzyskać nazwy artykułów, których aktualny stan magazynowy jest poniżej progu minimalnego, wpiszemy: select nazwa from artykuły where stock_actu<stock_mini. Jeśli chcemy uzyskać te pozycje w porządku alfabetycznym według nazw, wpiszemy: select nazwa from artykuły where stock_actu<stock_mini order by nazwa

Aby wykonać tego typu zapytanie, klasa Statement udostępnia metodę executeQuery:

    ResultSet executeQuery(String requête)

gdzie requête to tekst zapytania SELECT, które ma zostać wysłane.

Zatem, jeśli

  • connexion jest obiektem symbolizującym połączenie z bazą danych
  • Statement s=connexion.createStatement() tworzy obiekt Statement niezbędny do wysłania zapytań SQL
  • ResultSet rs=s.executeQuery(„ select nazwa from artykuły where stock_actu<stock_mini”) wykonuje zapytanie select i przypisuje wiersze wyników zapytania do obiektu typu ResultSet.

6.2.3.3. Klasa ResultSet: wynik zapytania SELECT

Obiekt typu ResultSet reprezentuje tabelę, czyli zbiór wierszy i kolumn. W danym momencie dostępny jest tylko jeden wiersz tabeli, zwany wierszem bieżącym. Podczas początkowego utworzenia obiektu typu ResultSet bieżącym wierszem jest wiersz nr 1, o ile obiekt typu ResultSet nie jest pusty. Aby przejść do następnego wiersza, klasa ResultSet udostępnia metodę next:

    boolean next()

Metoda ta próbuje przejść do następnego wiersza w ResultSet i zwraca true, jeśli operacja się powiedzie, a w przeciwnym razie zwraca false. W przypadku powodzenia następny wiersz staje się nowym wierszem bieżącym. Poprzedni wiersz zostaje utracony i nie ma możliwości cofnięcia się, aby go odzyskać. Tabela ResultSet zawiera kolumny col1, col2,.... Aby wykorzystać różne pola bieżącego wiersza, dostępne są następujące metody:

Type getType("coli") 

w celu uzyskania pola „coli” z bieżącego wiersza. Type określa typ pola coli. Dość często stosuje się metodę getString na wszystkich polach, co pozwala uzyskać zawartość pola jako ciąg znaków. Następnie, w razie potrzeby, dokonuje się konwersji. Jeśli nie znamy nazwy kolumny, można skorzystać z metod

Type getType(i) 

, gdzie i jest indeksem żądanej kolumny (i >= 1).

6.2.3.4. Pierwszy przykład

Oto program wyświetlający zawartość utworzonej wcześniej bazy danych ARTICLES:

import java.sql.*;
import java.io.*;

// wyświetla zawartość bazy systemowej ARTICLES

public class articles1{

    static final String DB="ARTICLES";        // baza danych do wykorzystania

    public static void main(String arg[]){

        Connection connect=null;        // połączenie z bazą
        Statement S=null;                // obiekt, do którego wysyłane są zapytania
        ResultSet RS=null;            // tabela wyników zapytania
        try{
            // połączenie z bazą
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // utworzenie obiektu Statement
            S=connect.createStatement();
            // wykonanie zapytania SELECT
            RS=S.executeQuery("select * from " + DB);
            // przetwarzanie tabeli wyników
            while(RS.next()){                // dopóki pozostaje wiersz do przetworzenia
                // wyświetla się go na ekranie
                System.out.println(RS.getString("code")+","+
                    RS.getString("nom")+","+
                    RS.getString("prix")+","+
                    RS.getString("stock_actu")+","+
                    RS.getString("stock_mini"));
            }// następny wiersz
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // zamknięcie bazy
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
        } catch (Exception e){}
    }// ręka

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasa    

Otrzymane wyniki są następujące:

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. Klasa ResultSetMetadata

W poprzednim przykładzie znamy nazwy kolumn w pliku ResultSet. Jeśli ich nie znamy, nie możemy skorzystać z metody getType(nom_colonne). Zamiast tego należy użyć metody getType(nr kolumny). Jednak aby uzyskać wszystkie kolumny, musielibyśmy wiedzieć, ile kolumn ma uzyskany plik ResultSet. Klasa ResultSet nie dostarcza nam tej informacji. Informację tę zapewnia nam klasa ResultSetMetaData. Ogólniej rzecz biorąc, klasa ta dostarcza nam informacji o strukturze tabeli, czyli o charakterze jej kolumn.

Dostęp do informacji o strukturze klasy ResultSet uzyskuje się poprzez utworzenie najpierw instancji obiektu klasy ResultSetMetaData. Jeśli RS jest obiektem typu ResultSet, to powiązany z nim obiekt ResultSetMetaData uzyskuje się poprzez:

    RS.getMetaData()

Warto zwrócić uwagę na dwie przydatne metody w klasie ResultSetMetaData:

  1. int getColumnCount(), która zwraca liczbę kolumn obiektu ResultSet
  2. String getColumnLabel(int i), która zwraca nazwę kolumny i z obiektu ResultSet (i >= 1)

6.2.3.6. Drugi przykład

Poprzedni program wyświetlał zawartość bazy danych ARTICLES. W tym miejscu piszemy program, który na bazie danych ARTICLES wykonuje każde zapytanie SQL lub Select, które użytkownik wpisze z klawiatury.

import java.sql.*;
import java.io.*;

// wyświetla zawartość bazy systemowej ARTICLES

public class sql1{

    static final String DB="ARTICLES";        // baza danych do wykorzystania

    public static void main(String arg[]){

        Connection connect=null;        // połączenie z bazą
        Statement S=null;                // obiekt, do którego wysyłane są zapytania
        ResultSet RS=null;            // tabela wyników zapytania
        String select;                    // tekst zapytania SQL select
        int nbColonnes;                // liczba kolumn w ResultSet

        // utworzenie strumienia danych z klawiatury
        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{
            // połączenie z bazą danych
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // utworzenie obiektu Statement
            S=connect.createStatement();
            // pętla wykonywania zapytań SQL wpisanych z klawiatury
            System.out.print("Requête : ");
            select=in.readLine();
            while(!select.equals("fin")){
                // wykonanie zapytania
                RS=S.executeQuery(select);
                // liczba kolumn
                nbColonnes=RS.getMetaData().getColumnCount();
                // przetwarzanie tabeli wyników
                System.out.println("Résultats obtenus\n\n");
                while(RS.next()){                // dopóki pozostaje wiersz do przetworzenia
                    // wyświetla się na ekranie
                    for(int i=1;i<nbColonnes;i++)
                        System.out.print(RS.getString(i)+",");
                    System.out.println(RS.getString(nbColonnes));
                }// następny wiersz
                // następne zapytanie
                System.out.print("Requête : ");
                select=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // zamknięcie bazy danych i strumienia wejściowego
        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);
    }
}// klasa    

Oto kilka uzyskanych wyników:

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. Wysyłanie żądania aktualizacji bazy danych

Obiekt typu Statement służy do przechowywania żądań typu SQL. Metoda, którą ten obiekt wykorzystuje do wysyłania żądań aktualizacji typu SQL (INSERT, UPDATE, DELETE) nie jest już metodą executeQuery, którą omówiono wcześniej, lecz metodą executeUpdate:

    int executeUpdate(String requête)

Różnica polega na wyniku: podczas gdy metoda executeQuery zwracała tabelę wyników (ResultSet), metoda executeUpdate zwraca liczbę wierszy, których dotyczy operacja aktualizacji.

6.2.3.8. Trzeci przykład

Wracamy do poprzedniego programu, który nieznacznie modyfikujemy: zapytania wpisywane z klawiatury są teraz zapytaniami o aktualizację bazy danych ARTICLES.

import java.sql.*;
import java.io.*;

// wyświetla zawartość bazy systemowej ARTICLES

public class sql2{

    static final String DB="ARTICLES";    // baza danych do wykorzystania

    public static void main(String arg[]){

        Connection connect=null;        // połączenie z bazą
        Statement S=null;                // obiekt, do którego wysyłane są zapytania
        ResultSet RS=null;            // tabela wyników zapytania
        String sqlUpdate;                // tekst zapytania SQL dotyczącego aktualizacji
        int nbLignes;                    // liczba wierszy objętych aktualizacją

        // utworzenie strumienia danych z klawiatury
        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{
            // połączenie z bazą danych
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // utworzenie obiektu Statement
            S=connect.createStatement();
            // pętla wykonywania zapytań SQL wpisanych z klawiatury
            System.out.print("Requête : ");
            sqlUpdate=in.readLine();
            while(!sqlUpdate.equals("fin")){
                // wykonanie zapytania
                nbLignes=S.executeUpdate(sqlUpdate);
                // monitorowanie
                System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                // kolejne zapytanie
                System.out.print("Requête : ");
                sqlUpdate=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // zamknięcie bazy danych i strumienia wejściowego
        try{
            // zwolnienie zasobów powiązanych z bazą
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // wyłączenie przepływu danych z klawiatury
            in.close();
        } catch (Exception e){}
    }// ręka

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasa    

Oto wyniki różnych uruchomień programów sql1 i sql2:

Lista wierszy bazy 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

Zmieniamy niektóre wiersze:


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

Weryfikacja:


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

Dodajemy wiersz:


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

Sprawdzenie:


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

Usuwamy wiersz:


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

Weryfikacja:


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. Wysłać dowolne zapytanie SQL

Obiekt Statement, niezbędny do wysyłania zapytań SQL, posiada metodę execute umożliwiającą wykonanie dowolnego typu zapytania SQL:

    boolean execute(String requête)

Zwracanym wynikiem jest wartość logiczna true, jeśli zapytanie zwróciło ResultSet (executeQuery), oraz false, jeśli zwróciło liczbę (executeUpdate). Uzyskaną wartość ResultSet można pobrać za pomocą metody getResultSet, a liczbę zaktualizowanych wierszy – za pomocą metody getUpdateCount. W ten sposób zapiszemy:


Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// wykonanie zapytania SQL
if (S.execute(requête)){
    // mamy zestaw wyników
    RS=S.getResultSet();
    // przetwarzanie ResultSet
    ...
} else {
    // było to zapytanie o aktualizację
    nbLignes=S.getUpdateCount();
    ...
}

6.2.3.10. Czwarty przykład

W programie sql3 wykorzystujemy koncepcję programów sql1 i sql2, dzięki czemu program ten jest teraz w stanie wykonać dowolne zapytanie SQL wpisane z klawiatury. Aby uczynić program bardziej uniwersalnym, charakterystyki bazy danych, z której ma korzystać, są przekazywane do programu jako parametry.

import java.sql.*;
import java.io.*;

// wywołanie: pg PILOTE URL UID MDP
// łączy się z bazą danych URL za pomocą klasy JDBC PILOTE
// użytkownik UID jest identyfikowany za pomocą hasła MDP

public class sql3{

    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){



        // sprawdzanie liczby argumentów
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);

        // inicjalizacja parametrów połączenia
        Connection connect=null;
        String uid="";
        String mdp="";
        if(arg.length>=3) uid=arg[2];
        if(arg.length==4) mdp=arg[3];        

        // inne dane
        Statement S=null;                        // obiekt wysyłania zapytań
        ResultSet RS=null;                    // tabela wyników zapytania
        String sqlText;                            // tekst zapytania SQL do wykonania
        int nbLignes;                                // liczba wierszy, których dotyczy aktualizacja
        int nbColonnes;                            // liczba kolumn w ResultSet

        // utworzenie strumienia danych z klawiatury
        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{
            // połączenie z bazą danych
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
            // utworzenie obiektu Statement
            S=connect.createStatement();
            // pętla wykonywania zapytań SQL wpisanych z klawiatury
            System.out.print("Requête : ");
            sqlText=in.readLine();
            while(!sqlText.equals("fin")){
                // wykonanie zapytania
                try{
                    if(S.execute(sqlText)){
                        // uzyskano wynik ResultSet – przetwarzamy go
                        RS=S.getResultSet();
                        // liczba kolumn
                        nbColonnes=RS.getMetaData().getColumnCount();
                        // przetwarzanie tabeli wyników
                        System.out.println("\nRésultats obtenus\n-----------------\n");
                        while(RS.next()){                // dopóki pozostaje wiersz do przetworzenia
                            // wyświetla się na ekranie
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// następny wiersz z ResultSet
                    } else {
                        // było to żądanie aktualizacji
                        nbLignes=S.getUpdateCount();
                        // kontynuacja
                        System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                    }//if
                } catch (Exception e){
                    System.out.println("Erreur " +e);
                }
                // kolejne żądanie
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // zamknięcie bazy i strumienia wejściowego
        try{
            // zwolnienie zasobów powiązanych z bazą
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // zamknięcie strumienia klawiatury
            in.close();
        } catch (Exception e){}
    }// main

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// klasa    

Tworzymy następujący plik zapytań:

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

Program uruchamia się w następujący sposób:

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

Program pobiera zatem dane wejściowe z pliku requetes, a wyniki zapisuje w pliku results. Uzyskane wyniki są następujące:

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 z bazą danych

Ostatnim razem, gdy zajmowaliśmy się problemem obliczania podatków, korzystaliśmy z interfejsu graficznego, a dane były przechowywane w pliku. Wracamy do tej wersji, zakładając teraz, że dane znajdują się w bazie danych ODBC-MySQL. MySQL to plik SGBD z domeny publicznej, który można wykorzystywać na różnych platformach, w tym Windows i Linux. Wraz z tym plikiem SGBD utworzono bazę danych dbimpots, zawierającą jedną tabelę o nazwie impots. Dostęp do bazy jest kontrolowany za pomocą loginu i hasła, w tym przypadku admimpots/mdpimpots. Zrzut ekranu pokazuje, jak korzystać z bazy dbimpots przy użyciu pliku 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

Interfejs graficzny aplikacji wygląda następująco:

Image

Interfejs graficzny został nieco zmodyfikowany:

nr
typ
nazwa
rola
1
JTextField
txtConnexion
ciąg połączenia z bazą danych ODBC
2
JScrollPane
JScrollPane1
kontener dla pola tekstowego typu „textarea” 3
3
JTextArea
txtStatus
wyświetla komunikaty o stanie, w szczególności komunikaty o błędach

Ciąg logowania wpisany w polu (1) ma następującą postać: DSN;login;hasło, przy czym

DSN
nazwa DSN źródła danych ODBC

login
identyfikator użytkownika posiadającego uprawnienia do odczytu w bazie

motdepasse
jego hasło

Baza danych dbimpots została utworzona ręcznie przy użyciu MySQL. Przekształca się ją w źródło danych ODBC w następujący sposób:

  • uruchamia się 32-bitowy menedżer źródeł danych ODBC

Image

  • użyj przycisku [Add], aby dodać nowe źródło danych ODBC

Image

  • wybieramy sterownik MySQL i wykonujemy [Terminer]

Image

  • sterownik MySQL wymaga podania pewnych informacji:
1
nazwa DSN, jaką należy nadać źródłu danych ODBC – może być dowolna
2
komputer, na którym działa SGBD MySQL – w tym przypadku localhost. Warto zauważyć, że bazą danych może być baza zdalna. Lokalne aplikacje korzystające ze źródła danych ODBC nie zauważyłyby tego. Dotyczyłoby to w szczególności naszej aplikacji Java.
3
baza danych MySQL, z której należy korzystać. MySQL to SGBD, który obsługuje relacyjne bazy danych, czyli zbiory tabel połączonych ze sobą relacjami. W tym miejscu podaje się nazwę obsługiwanej bazy danych.
4
nazwa użytkownika posiadającego uprawnienia dostępu do tej bazy
5
jego hasło

Po zdefiniowaniu źródła danych ODBC można przetestować nasz program:

 

Przyjrzyjmy się kodowi, który został zmodyfikowany w porównaniu z wersją graficzną bez bazy danych. Przypomnijmy sobie kod klasy impots używanej do tej pory:

// utworzenie klasy „impots”

public class impots{

    // dane niezbędne do obliczenia podatku
     // pochodzą ze źródła zewnętrznego

    private double[] limites, coeffR, coeffN;

     // konstruktor
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // sprawdzamy, czy wszystkie trzy tablice mają ten sam rozmiar
        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+")");
        // w porządku
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//producent

     // obliczenie podatku
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // obliczanie liczby udziałów
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // obliczenie podatku
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // zwrot wyniku
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//obliczyć
}//klasa

Ta klasa tworzy trzy tablice limites, coeffR, coeffN na podstawie trzech tablic przekazanych jako parametry do jej konstruktora. Postanowiono dodać do niej nowy konstruktor, umożliwiający utworzenie tych samych trzech tablic na podstawie danych z bazy danych:

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

    // dsnIMPOTS: nazwa bazy danych DSN
     // userIMPOTS, mdpIMPOTS: login/hasło dostępu do bazy

W tym przykładzie postanowiliśmy nie implementować tego nowego konstruktora w klasie impots, lecz w klasie pochodnej impotsJDBC:

// zaimportowane pakiety
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // dodanie konstruktora umożliwiającego tworzenie
   // tabel limitów, współczynników coeffr i coeffn na podstawie tabeli
   // podatków z bazy danych
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nazwa bazy danych DSN
     // userIMPOTS, mdpIMPOTS: login/hasło dostępu do bazy

     // tabele danych
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // połączenie z bazą danych
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // utworzenie obiektu Statement
    Statement S=connect.createStatement();
    // zapytanie SELECT
    String select="select limites, coeffr, coeffn from impots";
    // wykonanie zapytania
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // przetwarzanie bieżącego wiersza
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// następny wiersz
     // zamknięcie zasobów
    RS.close();
    S.close();
    connect.close();
     // przeniesienie danych do tablic ograniczonych
    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
  }//konstruktor
}//klasa

Konstruktor odczytuje zawartość tabeli impots z bazy danych przekazanej mu w parametrach i wypełnia trzy tablice: limites, coeffR oraz coeffN. Może wystąpić pewna liczba błędów. Konstruktor nie obsługuje ich, lecz „przekazuje” je do programu wywołującego:

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

Jeśli przyjrzymy się powyższemu kodowi, zauważymy, że klasa impotsJDBC korzysta bezpośrednio z pól limites, coeffR, coeffN ze swojej klasy bazowej impots. Pola te są zadeklarowane jako prywatne:

    private double[] limites, coeffR, coeffN;

klasa impotsJDBC nie ma bezpośredniego dostępu do tych pól. Wprowadzamy zatem pierwszą zmianę w klasie bazowej, wpisując:

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

Atrybut protected umożliwia klasom pochodnym klasy impots bezpośredni dostęp do pól zadeklarowanych za pomocą tego atrybutu. Musimy wprowadzić drugą zmianę. Konstruktor klasy potomnej impotsJDBC jest zadeklarowany w następujący sposób:

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

Wiadomo, że przed utworzeniem obiektu klasy potomnej należy najpierw utworzyć obiekt klasy nadrzędnej. W tym celu konstruktor klasy potomnej musi jawnie wywołać konstruktor klasy nadrzędnej za pomocą instrukcji super(....). Tutaj nie zostało to zrobione, ponieważ nie widać, który konstruktor klasy nadrzędnej można by wywołać. Na razie istnieje tylko jeden, ale nie jest on odpowiedni. Kompilator szuka wówczas w klasie nadrzędnej konstruktora bez argumentów, który mógłby wywołać. Nie znajduje go, co powoduje błąd kompilacji. Dodajemy więc konstruktor bez argumentów do naszej klasy impots:

   // pusty konstruktor
  protected impots(){}

Deklarujemy go jako „protected”, aby mógł być używany wyłącznie przez klasy potomne. Szkielet klasy impots wygląda teraz następująco:

public class impots{

  // dane niezbędne do obliczenia podatku
   // pochodzą ze źródła zewnętrznego

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

   // puste pole „producent”
  protected impots(){}

   // producent
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
  }//producent

   // obliczenie podatku
  public long calculer(boolean marié, int nbEnfants, int salaire){
.............
    }//obliczyć
}//klasa

Działanie menu Initialiser w naszej aplikacji wygląda następująco:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // pobieramy ciąg połączenia
    Pattern séparateur=Pattern.compile("\\s*;\\s*");
    String[] champs=séparateur.split(txtConnexion.getText().trim());
     // potrzebne  trzy pola
    if(champs.length!=3){
      // błąd
      txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
       // powrót do interfejsu graficznego
      txtConnexion.requestFocus();
      return;
    }//if
     // ładujemy dane
    try{
       // tworzenie obiektu impotsJDBC
      objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
       // potwierdzenie
      txtStatus.setText("Données chargées");
       // wynagrodzenie można zmienić
      txtSalaire.setEditable(true);
       // nie można już wprowadzać zmian
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // problem
      txtStatus.setText("Erreur : " + ex.getMessage());
      // koniec
      return;
    }//przechwycenie
  }

Po utworzeniu obiektu objImpots aplikacja działa identycznie jak już napisana aplikacja graficzna. Zachęcamy czytelnika do zapoznania się z nią.

6.4. Ćwiczenia

6.4.1. Ćwiczenie 1

Należy stworzyć interfejs graficzny dla poprzedniego programu sql3.

6.4.2. Ćwiczenie 2

Aplet Java może uzyskać dostęp do bazy danych wyłącznie za pośrednictwem serwera, z którego został załadowany. Ponieważ aplet nie ma dostępu do dysku komputera, na którym jest uruchamiany, baza danych nie może znajdować się na komputerze klienta korzystającego z apletu. Mamy zatem do czynienia z następującą sytuacją:

Image

Komputer, na którym uruchomiono aplet, serwer oraz komputer, na którym znajduje się baza danych, mogą być trzema różnymi komputerami. Zakładamy tutaj, że baza danych znajduje się na serwerze.

Zadanie 1

Napisz w języku Java następującą aplikację serwerową:

  • aplikacja serwerowa działa na porcie przekazanym jej jako parametr
  • gdy klient nawiązuje połączenie, aplikacja serwerowa wysyła komunikat
200 - Bienvenue - Envoyez votre requête
  • następnie klient wysyła parametry niezbędne do połączenia z bazą danych oraz zapytanie, które chce wykonać, w postaci:
Pilote Java/URL base/UID/MDP/requête SQL

Parametry są oddzielone ukośnikiem. Pierwsze cztery parametry dotyczą programu sql3 opisanego w tym rozdziale.

  • Aplikacja serwerowa nawiązuje następnie połączenie z określoną bazą danych, która musi znajdować się na tym samym komputerze co ona, i wykonuje na niej zapytanie SQL. Wyniki są zwracane do klienta w postaci:
100 - ligne1
100 - ligne2

...

jeśli jest to wynik zapytania SELECT lub

101 - nbLignes

w celu zwrócenia liczby wierszy, których dotyczy zapytanie aktualizacyjne. Jeśli wystąpi błąd połączenia z bazą danych lub błąd wykonania zapytania, aplikacja zwraca

500 - Message d’erreur
  • Po wykonaniu zapytania aplikacja serwerowa zamyka połączenie.

Zadanie 2

Stwórz aplet Java umożliwiający wysyłanie zapytań do wspomnianego serwera. Można się zainspirować interfejsem graficznym z poprzedniego ćwiczenia 1. Ponieważ port serwera może się zmieniać, należy go wprowadzić w interfejsie apletu. To samo dotyczy wszystkich parametrów niezbędnych do wysłania wiersza:

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

, którą klient musi wysłać do serwera.

6.4.3. Ćwiczenie 3

Poniższy tekst przedstawia problem, który pierwotnie miał być rozwiązany w języku Visual Basic. Należy go dostosować do przetwarzania w języku Java w aplecie. Apleta ta będzie korzystać z serwera z ćwiczenia 2. Interfejs graficzny można zmodyfikować, aby uwzględnić nowy kontekst wykonania.

Zamierzamy stworzyć aplikację ilustrującą różne możliwe operacje aktualizacji tabeli w bazie danych ACCESS. Baza danych ACCESS nosi nazwę articles.mdb. Zawiera ona jedną tabelę o nazwie „articles”, w której wymienione są artykuły sprzedawane przez firmę. Jej struktura jest następująca:

nazwa
typ
code
4-znakowy kod artykułu
nom
nazwa (ciąg znaków)
prix
jego cena (rzeczywista)
stock_actu
aktualny stan magazynowy (liczba całkowita)
stock_mini
minimalny stan magazynowy (liczba całkowita), poniżej którego należy uzupełnić zapasy danego artykułu

Proponujemy wyświetlanie i aktualizowanie tej tabeli za pomocą poniższego formularza:

Image

Kontrolki w tym formularzu są następujące:

nr
typ
nazwa
Funkcja
1
pole tekstowe
karta
numer wyświetlanej karty
parametr „enabled” ma wartość „false”
2
pole tekstowe
kod
kod artykułu
3
pole tekstowe
nazwa
nazwa artykułu
4
pole tekstowe
cena
cena produktu
5
pole tekstowe
aktualny
aktualny stan magazynowy produktu
6
pole tekstowe
minimum
minimalny stan magazynowy produktu
7
data
data1
Kontrola danych powiązana z bazą danych
nazwa_bazy_danych=ścieżka do pliku articles.mdb
recordsource=articles
connect=access
8
HScrollBar
pozycja
umożliwia poruszanie się po tabeli
9
przycisk
OK
służy do zatwierdzenia aktualizacji – pojawia się tylko podczas jej przeprowadzania
10
przycisk
Anuluj
umożliwia anulowanie aktualizacji – pojawia się tylko podczas jej przeprowadzania
11
frame
frame1
dla upiększenia
12
pole tekstowe
nazwa_podstawowa
nazwa otwartej bazy danych
parametr „enabled” ma wartość „false”
13
pole tekstowe
sourcename
nazwa otwartej tabeli
parametr „enabled” ma wartość „false”

Tworzenie arkusza w VB

kontrola
Szczególne cechy
data1
Pola „databasename” i „recordsource” są wypełnione. Pole „databasename” musi wskazywać bazę Access articles.mdb w Państwa katalogu, a pole „recordsource” – tabelę „articles”.
code
Jest to pole tekstowe, które chcemy powiązać z polem „code” bieżącego rekordu z „data1”. W tym celu wypełniamy dwa pola:
datasource: wpisujemy „data1”, aby wskazać, że pole tekstowe jest powiązane z tabelą powiązaną z „data1”
datafield: wybieramy pole „code” z tabeli „articles”
Po wykonaniu tych czynności pole tekstowe „code” będzie zawsze zawierało wartość pola „code” z bieżącego rekordu w tabeli data1. I odwrotnie, zmiana zawartości tego pola tekstowego spowoduje zmianę wartości pola „code” w bieżącej karcie.
To samo dotyczy pozostałych pól tekstowych
nom
źródło danych: data1 pole danych: nazwa
prix
źródło danych: data1 pole danych: cena
actuel
źródło danych: data1 pole danych: stock_actu
minimum
źródło danych: data1 pole danych: stock_mini

Menu

Struktura menu jest następująca

Edycja
Przeglądaj
Zamknij
Dodaj
Poprzedni
 
Edytuj
Dalej
 
Usuń
Pierwszy
 
 
Ostatni
 

Rola poszczególnych opcji jest następująca:

menu
nazwa
funkcja
Ajouter
mnuadd
w celu dodania nowego rekordu do tabeli artykułów
Supprimer
mnusupprimer
aby usunąć z tabeli artykułów aktualnie wyświetlany wpis
Modifier
mnumodify
aby zmodyfikować aktualnie wyświetlany rekord w tabeli artykułów
Précédent
mnuprecedent
przejście do poprzedniego rekordu
Suivant
mnusuivant
przejdź do następnego wpisu
Premier
mnupremier
aby przejść do pierwszego wpisu
Dernier
mnudernier
przejdź do ostatniego wpisu
Quitter
mnuquitter
aby zamknąć aplikację

Ładowanie arkusza

Podczas zdarzenia form_load otwierana jest tabela powiązana z data1 (data1.refresh). Jeśli otwarcie nie powiedzie się, wyświetlany jest komunikat o błędzie, a program się kończy (end). W przeciwnym razie wyświetlany jest formularz z pierwszym rekordem z tabeli articles. Nie ma możliwości wprowadzania danych w polach (właściwość enabled ustawiona na false). Wprowadzanie danych jest możliwe wyłącznie za pomocą opcji Dodaj i Edytuj. Przyciski OK i Anuluj są ukryte (visible=false).


Część 1

Zamierzamy stworzyć procedury związane z różnymi opcjami menu, a także z przyciskami OK i Anuluj. Na początek pominiemy następujące kwestie:

  • włączanie/wyłączanie niektórych opcji menu: na przykład opcja „Dalej” musi być wyłączona, jeśli kursor znajduje się na ostatnim rekordzie tabeli
  • obsługa poziomego paska przewijania

menu Przeglądaj/Dalej

  • przechodzi do następnego rekordu (data1.RecordSet.MoveNext), jeśli nie znajdujemy się na końcu pliku (data1.RecordSet.EOF). Aktualizuje pole tekstowe karty (data1.recordset.absoluteposition/ data1.recordset.recordcount).

menu Przeglądaj/Poprzedni

  • przechodzi do poprzedniego rekordu (data1.RecordSet.MovePrevious), jeśli nie znajduje się na początku pliku (data1.RecordSet.BOF). Aktualizuje pole tekstowe karty.

menu Przeglądaj/Pierwszy

  • przechodzi do pierwszego rekordu (data1.RecordSet.MoveFirst), jeśli plik nie jest pusty (data1.recordset.recordcount=0). Aktualizuje pole tekstowe karty.

menu Przeglądaj/Ostatni

  • przechodzi do ostatniego rekordu (data1.RecordSet.MoveLast), jeśli plik nie jest pusty (data1.recordset.recordcount=0). Aktualizuje pole tekstowe karty.

menu Edycja/Dodaj

  • umożliwia dodanie rekordu do tabeli
  • przełącza w tryb dodawania rekordu (data1.recordset.addnew)
  • zezwala na wprowadzanie danych w 5 polach: kod, nazwa, cena... (enabled=true)
  • blokuje menu Edycja, Przeglądaj, Wyjdź (enabled=false)
  • wyświetla przyciski OK i Anuluj (visible=true)

przycisk OK

  • zatwierdza zmianę rekordu (data1.recordset.Update)
  • ukrywa przyciski OK i Anuluj (visible=false)
  • włącza opcje Edytuj, Przeglądaj, Zamknij (enabled=true)
  • aktualizuje pole tekstowe karty

przycisk Anuluj

  • unieważnia zmianę w rekordzie (data1.recordset.CancelUpdate)
  • ukrywa przyciski OK i Anuluj (visible=false)
  • włącza opcje Edytuj, Przeglądaj, Zamknij (enabled=true)
  • aktualizuje pole tekstowe karty

menu Edycja/Edytuj

  • umożliwia edycję rekordu wyświetlanego w formularzu
  • przełącza w tryb edycji rekordu (data1.recordset.edit)
  • zezwala na wprowadzanie danych w 4 polach: nazwa, cena... (enabled=true), ale nie w polu kod (enabled=false)
  • blokuje menu Edycja, Przeglądaj, Zamknij (enabled=false)
  • wyświetla przyciski OK i Anuluj (visible=true)

menu Edycja/Usuń

  • umożliwia usunięcie (data1.recordset.delete) wyświetlanego rekordu z tabeli
  • przechodzi do następnego rekordu (data1.recordset.movenext)

menu Zamknij

  • wyładowuje arkusz (unload me)

zdarzenie form_unload (cancel as integer)

  • wywoływane przez operację „unload me” lub zamknięcie formularza za pomocą skrótu Alt-F4 lub podwójnego kliknięcia w polu systemowym, a więc niekoniecznie przez opcję „Wyjdź”.
  • wyświetla pytanie „Czy na pewno chcesz zamknąć aplikację?” z dwoma przyciskami Tak/Nie (msgbox ze stylem=vbyes+vbno)
  • jeśli odpowiedź brzmi „Nie” (=vbno), ustawiamy wartość „cancel” na -1 i wychodzimy z procedury form_unload. Wartość „cancel” równa -1 oznacza, że zamknięcie okna zostało odrzucone.
  • Jeśli odpowiedź brzmi „Tak” (=vbyes), baza danych zostaje zamknięta (data1.recordset.close, data1.database.close).

Część 2 – Zarządzanie menu

W tym miejscu skupiamy się na zezwalaniu/blokowaniu menu. Po każdej operacji zmieniającej bieżący rekord wywołamy procedurę, którą możemy nazwać Oueston. Procedura ta sprawdzi następujące warunki:

. jeśli plik jest pusty,

  • zablokujemy menu Przeglądaj, Edytuj/Usuń,
  • a pozostałe

. jeśli bieżąca karta jest pierwszą kartą,

  • zablokuje się opcje „Przeglądaj” i „Poprzedni”
  • pozostale opcje będą dostępne

. jeśli bieżąca karta jest ostatnią,

  • zablokuje się opcje „Przeglądaj” i „Dalej”
  • pozostałe przyciski będą aktywne

Część 3 – Obsługa suwaka poziomego

Regulator poziomy posiada trzy ważne pola:

  • min: jego wartość minimalna
  • max: jego wartość maksymalna
  • value: jego aktualna wartość

Inicjalizacja falownika

Podczas ładowania (form_load) falownik zostanie zainicjowany w następujący sposób:

  • min=0
  • max=data1.recordset.recordcount-1
  • value=1

Należy zauważyć, że zaraz po otwarciu bazy danych (data1.refresh) liczba rekordów w tabeli reprezentowanej przez data1.recordset.recordcount jest nieprawidłowa. Aby wartość ta była poprawna, należy przejść na koniec tabeli (MoveLast), a następnie powrócić na początek tabeli (MoveFirst).

Bezpośrednia obsługa falownika

Pozycja kursora na regulatorze odpowiada pozycji w tabeli.

Gdy użytkownik zmienia ustawienie regulatora (określone tutaj jako „pozycja”), wyzwalane jest zdarzenie position_change. W ramach tego zdarzenia zmienimy bieżący rekord tabeli, tak aby odzwierciedlał on przesunięcie dokonane na regulatorze. W tym celu wykorzystamy pole „absoluteposition” zdarzenia data1.recordset. Po przypisaniu wartości i do tego pola rekord nr i w tabeli staje się rekordem bieżącym. Wpisy są numerowane od 0, a zatem mają numery z przedziału [0,data1.recordset.recordcount-1]. W procedurze position_change wystarczy wpisać

    data1.recordset.absoluteposition=position.value

aby aktualna karta wyświetlana w formularzu odzwierciedlała zmianę wprowadzoną na falowniku.

Po wykonaniu tej czynności wywołamy procedurę Oueston w celu aktualizacji menu.

Aktualizacja przetwornicy

Ponieważ pozycja suwaka falownika musi odzwierciedlać pozycję w tabeli, należy aktualizować wartość falownika za każdym razem, gdy następuje zmiana aktualnego rekordu w tabeli, spowodowana przez którekolwiek z menu. Ponieważ każde z nich wywołuje procedurę Oueston, najlepiej jest umieścić tę aktualizację również w tej procedurze. Wystarczy wpisać tutaj:

    position.value=data1.recordset.absoluteposition

Część 4 – Opcja „Szukaj”

Dodajemy opcję Przeglądaj/Szukaj, której celem jest umożliwienie użytkownikowi wyświetlenia artykułu na podstawie podanego kodu.

Gdy opcja ta jest włączona, zachodzą następujące sekwencje:

  • przechodzi się do trybu Dodawania karty (Addnew), wyłącznie po to, aby nie modyfikować bieżącej karty, na której znajdowaliśmy się w momencie włączenia opcji,
  • zezwala się na wprowadzanie danych w polu „kod” i usuwa się zawartość pola „karta”,
  • blokuje się menu i wyświetla przyciski OK oraz Anuluj
  • gdy użytkownik naciśnie przycisk OK, musimy wyszukać rekord odpowiadający kodowi wpisanemu przez użytkownika. Procedura OK_click jest jednak już wykorzystywana w opcjach Przeglądaj/Dodaj oraz Przeglądaj/Edytuj. Aby rozróżnić te przypadki, musimy zarządzać zmienną globalną, którą nazwiemy tutaj „stan” i która będzie miała trzy możliwe wartości: „dodaj”, „edytuj” oraz „wyszukaj”. Procedury powiązane z przyciskami OK i Anuluj będą korzystać z tej zmiennej, aby określić, w jakim kontekście zostały wywołane.
  • Jeśli stan ma wartość „szukaj”, w procedurze powiązanej z przyciskiem OK
    • anuluje się operację addnew (data1.recordset.cancelupdate), ponieważ nie zamierzano dodawać rekordu. Należy zauważyć, że wówczas bieżący rekord ponownie staje się tym, który był widoczny na ekranie przed operacją Przeglądaj/Szukaj.
    • tworzy się kryterium wyszukiwania związane z kodem i uruchamia się wyszukiwanie (data1.recordset.findfirst kryterium),
    • jeśli wyszukiwanie zakończy się niepowodzeniem (data1.recordset.nomatch=true), informuje się o tym użytkownika, a następnie powraca się do trybu dodawania (addnew) i opuszcza procedurę OK. Użytkownik będzie musiał ponownie wprowadzić nowy kod lub wybrać opcję Anuluj.
    • jeśli wyszukiwanie zakończy się powodzeniem, znaleziony rekord staje się nowym rekordem bieżącym. Przywraca się menu, ukrywa przyciski OK/Anuluj, blokuje wprowadzanie danych w polu kodu i kończy procedurę.
  • . Jeśli stan wynosi „szukaj”, w procedurze powiązanej z opcją Anuluj
    • anuluje się operację addnew (data1.recordset.cancelupdate). Następnie nastąpi automatyczny powrót do aktualnego rekordu sprzed operacji Przeglądaj/Szukaj.
    • przywraca menu, ukrywa przyciski OK/Anuluj, blokuje wprowadzanie danych w polu kodu i kończy procedurę.

Część 5 – Zarządzanie kodem

Artykuł musi być jednoznacznie identyfikowany za pomocą swojego kodu. Należy zapewnić, aby w opcji Przeglądaj/Dodaj dodanie zostało odrzucone, jeśli rekord, który ma zostać dodany, ma kod artykułu już istniejący w tabeli.

6.4.4. Ćwiczenie 4

Przedstawiamy tutaj aplikację internetową opartą na serwerze z ćwiczenia 2. Jest to zalążek aplikacji e-commerce.

Klient zamawia produkty za pomocą następującego interfejsu internetowego:

Image

Może on wykonać następujące czynności:

  • wybiera produkt z listy rozwijanej
  • określa żądaną ilość
  • potwierdza zakup przyciskiem Acheter
  • zakup pojawia się na liście zakupionych produktów
  • może usunąć produkty z tej listy, wybierając produkt i korzystając z przycisku Retirer
  • po naciśnięciu przycisku Bilan otrzymuje następujące podsumowanie:

Image

Podsumowanie pozwala użytkownikowi zapoznać się ze szczegółami faktury. Użytkownik ma dostęp do szczegółów dotyczących pozycji wybranej z listy rozwijanej za pomocą przycisku Informations:

Image

Po wygenerowaniu zestawienia faktury użytkownik może ją zatwierdzić na kolejnej stronie. W tym celu musi podać swój adres e-mail i potwierdzić zamówienie, klikając odpowiedni przycisk.

Image

Przed zapisaniem zamówienia aplikacja prosi o potwierdzenie:

Image

Po potwierdzeniu zamówienia aplikacja przetwarza je i wyświetla stronę potwierdzenia:

Image

W rzeczywistości aplikacja nie przetwarza zamówienia. Ogranicza się jedynie do wysłania wiadomości e-mail do użytkownika z prośbą o uregulowanie kwoty za zakupy:

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

Pytanie: Stwórz odpowiednik tej aplikacji internetowej za pomocą apletu Java.