La technologie JDBC (Java DataBase Connectivity) est un ensemble de classes permettant
de développer des applications capables de se connecter à des
serveurs de bases de données (SGBD).
Dans un système client/serveur, l'accès
aux bases de données avec JDBC peut s'effectuer selon un modèle à deux couches
ou bien un modèle à trois couches.
Pour le modèle à deux couches, une application Java est intimement liée
avec une base de données. A cet effet, il faut bien évidemment disposer,
pour la base de données concernée, d'un pilote JDBC adéquat.
Les instructions SQL sont directement envoyées à la base, cette dernière
renvoyant les résultats par un biais tout aussi direct. La base de données peut être
exécutée sur la machine locale (celle sur laquelle l'application Java fonctionne)
ou bien sur tout autre ordinateur du réseau (Intranet ou Internet).
Dans le modèle à 3 couches, une troisième couche (le serveur d'application)
vient s'intercaler entre l'application Java et la base de données. Les instructions SQL
et les résultats livrés en retour y transitent. Ce modèle présente
l'avantage d'intégrer dans cette couche un contrôle d'accès.
Les pilotes actuels sont classés en quatre catégories :
- les passerelles JDBC-ODBC, avec le pilote ODBC appartenant à une base de données.
Le pilote convertit les appels de données Java en appel ODBC valide, et les exécute
ensuite à l'aide du pilote ODBC
- un mélange de pilotes natifs et de pilotes Java. Les appels JDBC sont convertis en appels natifs
pour le serveur de bases de données (Oracle, Sybase, ou autres).
- les pilotes convertissant les appels JDBC en un protocole indépendant du SGBD.
Un serveur convertit ensuite ceux-ci dans le protocole SGBD requis (modèle à 3 couches)
- les pilotes JDBC, convertissant les appels JDBC directement en un protocole réseau exploité
par le SGBD. Cette solution est à préconiser dans le cadre d'un intranet
A l'heure actuelle, les bases de données disposent pratiquement toutes d'un pilote ODBC,
étant donné que nombre d'entre-elles ont été développées
à partir d'interfaces Microsoft.
ODBC signifie Open Database Connectivity. Il s'agit d'un format propriétaire
défini par Microsoft permettant la communication entre des clients pour bases de données fonctionnant sous Windows
et les SGBD du marché.
Etant donné qu'il existe différents types de bases de données, et que chacune d'entre-elles
possède sa propre façon de traiter les requêtes SQL et de renvoyer les résultats, ODBC
permet d'obtenir des résultats identiques quel que soit le type de base de données, sans avoir à
modifier le programme d'appel qui transmet la requête.
ODBC n'est toutefois pas la solution miracle, étant donné d'une part qu'il ne s'agit
pas d'une technologie libre (Microsoft la fait évoluer à son gré), d'autre part
qu'il ne s'agit que d'une interface "alternative" à celle fournie par les constructeurs
et compliquée à maîtriser.
Afin d'illustrer la connexion à une base de données, nous allons nous connecter
à une base Access par l'intermédiaire de la passerelle ODBC-JDBC, en abordant toutes
les étapes de la connexion à une base de données.
Après création de la base sous Access, il faut dans un premier temps intégrer
la base dans l'administrateur de source de données ODBC (Panneau de configuration / source de données ).
Les étapes de création de la source ODBC sont:
- la sélection du pilote pour la base de données concernée (dans notre cas le pilote Microsoft Access Driver)
- la définition du nom de la base et de son chemin d'accès
- la définition du nom de la source de données et du mot de passe
L'API (Application Programming Interface) JDBC, c'est-à-dire la bibliothèque
de classes JDBC, se charge de trois étapes indispensables à la connexion à
une base de données:
- la céation d'une connexion à la base
- l'envoi d'instructions SQL
- l'exploitation des résultats provenant de la base
Tous les objets et les méthodes relatifs aux bases de données sont
présentes dans le package java.sql, il est donc indispensable d'importer
java.sql.* dans tout programme se servant de la technologie JDBC.
Pour se connecter à une base de données déclarée
dans l'administrateur ODBC, il est essentiel de charger dans un premier temps le pilote
JDBC-ODBC (appelé pont JDBC-ODBC) :
Class.forname("sun.jdbc.odbc.JdbcOdbcDriver");
Cette instruction charge le pilote et crée une instance de cette classe. Pour se connecter à
une base de données particulière, il s'agit ensuite de créer une instance de la
classe Connection en indiquant la base de données à charger à l'aide de son URL
String url = "jdbc:odbc:base_de_donnees";
Connection con = DriverManager.getConnection(url);
Le nom de la base de données (ici base_de_donnees) étant celle
déclarée dans le panneau de configuration ODBC. La syntaxe de l'URL peut
varier légèrement selon le type de la base de données. Il s'agit
généralement d'une adresse de la forme:
jdbc:sousprotocole:nom
Pour exécuter une requête SQL, il s'agit dans un premier temps de
créer un objet Statement, pouvant être obtenu à partir
de l'objet Connection. Un objet ResultSet permettra de récupérer
les données en provenance de l'objet Statement.
String query = "SELECT * FROM Ma_Table;";
ResultSet results;
try {
Statement stmt = con.createStatement();
results = stmt.executeQuery(query);
}
catch(Exception(e){
System.out.println("exception du a la requete");
}
Une fois la connexion à la base de données établie, il est possible de
demander des informations sur le nom des tables et le contenu de chaque colonne, ainsi que
d'exécuter des requêtes SQL afin de récupérer
des informations, d'en ajouter ou bien de les modifier.
Les objets utilisables pour obtenir des informations sur la base sont:
- DataBaseMetaData: Un objet contenant les informations sur la base de données
en général (des métadonnées), c'est-à-dire le nom de la table,
les index, la version de la base, ...
- ResultSet: Un objet contenant les informations sur une table ou le résultat
d'une requête. L'accès aux données se fait colonne par colonne, mais il est éventuellement
possible d'accèder indépendamment à chaque colonne par son nom
- ResultSetMetaData: Un objet contenant des informations sur le nom et le type des colonnes d'une table
A chacun de ces objets est associé un grand nombre de méthodes permettant d'obtenir des informations
très détaillées sur les éléments de la base de données.
Toutefois, seul un nombre réduit des méthodes de chacun de ces objets permet
d'obtenir les informations les plus importantes sur les données.
L'objet ResultSet est l'objet le plus important de la technologie JDBC, car il s'agit d'une abstraction
de la table ou de la réponse à une requête (généralement un sous-ensemble d'une table).
Ainsi, presque toutes les méthodes et requêtes retournent les données sous forme
d'un objet ResultSet. Cet objet contient un nombre donné de colonnes repérées chacune
par un nom, ainsi qu'une ou plusieurs lignes contenant les données, et auxquelles il est possible
d'accèder séquentiellement une à une du haut vers le bas. Ainsi, afin d'exploiter un objet
ResultSet, il est nécessaire de récupérer le nombre de
colonnes de celui-ci, à l'aide de l'objet ResultSetMetaData.
ResultSetMetaData rsmd;
rsmd = results.getMetaData();
numcols = rsmd.getColumnCount();
Lors de l'obtention d'un objet ResultSet, le descripteur pointe avant la première ligne.
La méthode next() permet d'obtenir chacune des lignes suivantes, et retourne false
lorsqu'il ne reste plus aucune ligne. Etant donné que l'extraction de données de la base
peut générer des erreurs, il est indispensable d'inclure ces manipulations dans
un bloc d'exception (try).
Les données contenues dans le ResultSet peuvent être obtenues sous
différentes formes selon le type de données stockées dans chaque colonne.
Le contenu de chaque colonne peut être obtenu soit par le nom de celle-ci, soit par son
numéro (sachant que les numéros de colonne commencent à 1 et non à 0).
Les principales méthodes de l'objet ResultSet sont les suivantes:
- getInt(int): récupère sous forme d'entier le contenu d'une colonne désignée par son numéro
- getInt(String): récupère sous forme d'entier le contenu d'une colonne désignée par son nom
- getFloat(int): récupère sous forme de flottant le contenu d'une colonne désignée par son numéro
- getFloat(String): récupère sous forme de flottant le contenu d'une colonne désignée par son nom
- next(): déplace le pointeur de ligne sur la ligne suivante
- close(): "ferme" l'objet
- getMetaData(): retourne les métadonnées de l'objet (l'objet ResultSetMetaData)
L'objet ResultSetMetaData (obtenu de l'objet ResultSet) permet de connaître le nombre, le nom et le type
de chaque colonne à l'aide des méthodes suivantes:
- getColumnCount(): récupère le nombre de colonnes
- getColumnName(int): récupère le nom de la colonne spécifiée
- getColumnLabel(int): récupère le label de la colonne spécifiée
- getColumnType(int): récupère le type de données de la colonne spécifiée
L'objet DataBaseMetaData permet d'obtenir des informations sur la base de données entière.
Il sert notamment à récupérer le nom des tables contenues dans la base de données.
De plus, étant donné que de nombreuses bases de données supportent des variantes du langage SQL,
il existe de nombreuses méthodes associées à l'objet DataBaseMetaData permettant de
connaître les méthodes SQL supportées par la base.
Puisque l'accès à une base de données nécessite l'utilisation
conjointe de plusieurs objets, il peut être intéressant de créer quelques
objets de plus haut niveau encapsulant la plupart des comportements cités ci-dessus.
Ainsi la création d'un objet DataBase pour permettre d'encapsuler
l'ensemble des objets nécessaires à la connexion à une base de données
(Connection, Statement, DataBaseMetaData), ainsi que de (re)définir
des méthodes simples permettant de rendre plus simples certaines opérations,
comme la création de la connexion, la récupération du nom des tables, ainsi
qu'une méthode Execute rendant l'exécution de requête triviale.
class Database
{
Connection con;
resultSet results;
ResultSetMetaData rsmd;
DatabaseMetaData dm;
String catalog;
String types[];
//----------------------------
public Database(String Driver)
{
types = new String[1];
types[0] = "TABLES";
try {
Class.forName(driver);
}
catch(Exception e){
System.out.println("Erreur lors du chargement du driver:"+ e.getMessage());
}
}
//------------------------------------------------------
public void Open(String url,String login,String password)
{
try{
con = DriverManager.getConnection(url,login,password);
dma = con.getMetaData();
results = new resultSet(dma.getCatalogs());
String s[];
while(results.hasMoreElements()) {
s = results.NetxElement();
}
}
catch(Exception e){
System.out.println("echec d'ouverture:"+e.getMessage());
}
}
//-----------------
public void Close()
{
try{
con.close();
}
catch(Exception e){
System.out.println("echec lors de la fermeture:"+e.getMessage());
}
}
//-----------------------------
public String[] getTableNames()
{
String[] tbnames = null;
Vector tname = new Vector();
try{
results = new resultSet(dma.getTables(catalog,null,"%",types));
while (results.hasMoreElements())
tname.addElement(results.getColumnValue("TABLE_NAME"));
}
catch(Exception e){
System.out.println(e.getMessage());
}
tbnames = new String[tname.size()];
for(int i=0;i<tname.size();i++)
tbnames[i] = (String)tname.elementAt(i);
return tbnames;
}
//--------------------------------
public String[] getTableMetaData()
{
results = null;
try{
results = new resultSet(dma.getTables(catalog,null,"%",types));
}
catch(Exception e){
System.out.println(e.getMessage());
}
return results.getMetaData();
}
//------------------------------------------------
public String[] getColumnMetaData(String tablename)
{
results = null;
try{
results = new resultSet(dma.getTables(catalog,null,tablename,null));
}
catch(Exception e){
System.out.println(e.getMessage());
}
return results.getMetaData();
}
//------------------------------------------------
public String[] getColumnNames(String table)
{
String[] tbnames = null;
Vector tname = new Vector();
try{
results = new resultSet(dma.getTables(catalog,null,table,null));
while (results.hasMoreElements())
tname.addElement(results.getColumnValue("COLUMN_NAME"));
}
catch(Exception e){
System.out.println(e.getMessage());
}
tbnames = new String[tname.size()];
for(int i=0;i<tname.size();i++)
tbnames[i] = (String)tname.elementAt(i);
return tbnames;
}
//------------------------------------------------
public void getColumnValue(String table, String columnName)
{
try{
if (table.length()>0)
results = Execute("Select "+columnName+" from "+table+" order by "+columnName);
}
catch(Exception e){
System.out.println("Erreur sur la valeur de la colonne "
+columnName+e.getMessage());
}
}
//------------------------------------------------
public String getNextValue(String columnName)
{
String res = "";
try{
if (results.hasMoreElements())
res = results.getColumnvalue(columnName);
}
catch(Exception e){
System.out.println("Erreur sur la valeur suivante "+columnName+e.getMessage());
}
return res;
}
//------------------------------------------------
public resultSet Execute(String sql)
{
results = null;
try{
Statement stmt = con.createStatement();
results = new resultSet(stmt.executeQuery(sql));
}
catch(Exception e){
System.out.println(e.getMessage());
}
return results;
}
}
De la même façon, un nouvel objet resultSet (avec un r minuscule)
peut être intéressant s'il permet de façon transparente de retourner
automatiquement les résultats sous forme d'un tableau de valeurs, ainsi qu'en encapsulant
le nombre et le nom des colonnes contenues dans l'objet resultSet...
|