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:

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

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:

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:
- Połączenie z bazą danych
- Wysyłanie zapytań SQL do bazy danych
- Odbiór i przetwarzanie wyników tych zapytań
- 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:


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

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:
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:
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ć:
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:
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ć:
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.
- Tworzy się połączenie ze wszystkimi tabelami, które znajdują się za słowem kluczowym „from”
- Zachowane są tylko kolumny występujące po słowie kluczowym „select”
- Zachowane są wyłącznie wiersze spełniające warunek określony przez słowo kluczowe `where`
- 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:
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:
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:
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
, 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:
Warto zwrócić uwagę na dwie przydatne metody w klasie ResultSetMetaData:
- int getColumnCount(), która zwraca liczbę kolumn obiektu ResultSet
- 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:
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:
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:
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 été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS 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:

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
nazwa DSN źródła danych ODBC | |
| identyfikator użytkownika posiadającego uprawnienia do odczytu w bazie |
| 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

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

- wybieramy sterownik MySQL i wykonujemy [Terminer]

- 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:
klasa impotsJDBC nie ma bezpośredniego dostępu do tych pól. Wprowadzamy zatem pierwszą zmianę w klasie bazowej, wpisując:
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:
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 są 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ą:

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
- następnie klient wysyła parametry niezbędne do połączenia z bazą danych oraz zapytanie, które chce wykonać, w postaci:
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:
...
jeśli jest to wynik zapytania SELECT lub
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
- 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:
, 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:

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” |
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 |
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 |
mnuadd | w celu dodania nowego rekordu do tabeli artykułów | |
mnusupprimer | aby usunąć z tabeli artykułów aktualnie wyświetlany wpis | |
mnumodify | aby zmodyfikować aktualnie wyświetlany rekord w tabeli artykułów | |
mnuprecedent | przejście do poprzedniego rekordu | |
mnusuivant | przejdź do następnego wpisu | |
mnupremier | aby przejść do pierwszego wpisu | |
mnudernier | przejdź do ostatniego wpisu | |
mnuquitter | aby zamknąć aplikację |
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).
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).
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
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ć
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:
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ę.
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:

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:

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:

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.

Przed zapisaniem zamówienia aplikacja prosi o potwierdzenie:

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

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.

