====== JAVA ET LES BASES DE DONNÉES ====== __Notions__ : JDBC, pont JDBC-ODBC, Curseurs, Métadonnées, Persistance ===== INTRODUCTION ===== Java, comme tout bon langage de programmation, permet l'accès aux bases de données pour stocker les informations. Pour ce faire, //Sun//, l'éditeur historique du langage, a créé la fonctionnalité **//JDBC//** (//Java DataBase Connectivity//), qui permet d'établir une interface standardisée entre n'importe quel SGBD et le langage. Pour rester compatible avec les pilotes //ODBC// du monde Windows, java implémente un pont JDBC-ODBC. Par ailleurs, une autre technique consiste à enregistrer sur un support physique les objets créés en mémoire (fichier). On parle de **//persistance//** en langage objet, et de sérialisation en Java. Cette technique est aussi utilisée lorsque l’on souhaite faire transiter des objets à travers le réseau. ===== I CONFIGURATION DE JAVA ===== ==== 1 - PONT JDBC-ODBC ==== La première étape est de demander à Java d'établir le lien avec l'outil //ODBC//. La manière la plus simple de le réaliser est d'écrire la ligne suivante, où les termes en gras sont les mots clés de java (attention aux majuscules/minuscules) : Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //installe le pont JDBC-ODBC Cette ligne est à ajouter dans chaque classe où une connexion aux bases de données est requise. Remarque : il est possible de réaliser des connexions directes en //JDBC//, sans passer par //ODBC//. Il faudra alors trouver les pilotes //JDBC// appropriés à la base de données à laquelle on se connecte. ==== 2 - ÉTABLISSEMENT DE CONNEXION ==== Il faut ensuite faire le lien avec le pilote //ODBC// particulier créé précédemment. Il faudra avoir importé les outils SQL de java : import java.sql.* //à placer en tête du fichier .java (par exemple une applet) … Public class ___{… … //code à positionner dans une méthode Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver"); Connection cnx = DriverManager.getConnection ("jdbc:odbc:GQCM","",""); //crée une connection avec un pilote ODBC cnx.close(); //ferme la connexion au pilote en fin d’application ==== 3 LES BASES ET LA GESTION D'ERREUR ==== Toute interaction avec une base de données est susceptible de générer des erreurs : Pilote erroné, table vide, requête mal formée, dépassement de la fin d'un curseur, etc. Java impose donc d'encadrer toute action (ou toute suite d'actions) sur une table par un **//try {}//** et d'intercepter les erreurs (//Exception//) dans un **//catch{}//**. Le code doit donc être écrit de la sorte : //code à positionner dans une méthode try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") Connection cnx = DriverManager.getConnection("jdbc:odbc:GQCM","",""); //crée une connection avec un pilote ODBC } catch (Exception e) {e.printStackTrace();} … try {cnx.close(); //ferme la connection au pilote} catch (Exception e) {e.printStackTrace();} ===== II REQUÊTES ===== Pour exécuter ensuite des requêtes de création, de modification ou de sélection, il faudra manipuler les objets //Statement// (qui sont les **assertions** SQL) et //ResultSet// (//RecordSet// en //VB//). ==== 1 L'OBJET STATEMENT ==== Il s'agit de l'objet contenant les requêtes SQL à faire exécuter sur la base de données. Il peut s'agir de requêtes de sélection, qui retourneront alors un objet //ResultSet//, ou d'action (//INSERT, UPDATE// et //DELETE//) qui retourneront un booléen. __**Exemple d'utilisation**__ //Déclare un objet Statement appelé stmt Statement stmt ; //crée à partir de la connexion un nouvel objet Statement stmt = cnx.createStatement( ); //exécute la requête et stocke le résultat dans une variable i int i = stmt.executeUpdate("Insert into Propositions values (5,'Autre choix',False, False);") ; //effectue une validation des commandes opérées i = stmt.executeUpdate("commit;") ; //exécute la requête sélection et stocke le résultat dans une variable Resultset ResultSet rs = stmt.executeQuery("Select * from propositions"); ==== 2 L'OBJET RESULTSET ==== C'est un curseur permettant de parcourir les résultats obtenus lors d'une interrogation de base de données. __**Exemple d'utilisation**__ (penser à mettre les //try// et //catch// nécessaires) Connection con = DriverManager.getConnection("jdbc:odbc:GQCM","",""); //crée une connection avec un pilote ODBC //Déclare un objet Statement appelé stmt Statement stmt ; //crée à partir de la connexion un nouvel objet Statement stmt = con.createStatement( ); //exécute la requête et stocke le résultat dans une variable i int i = stmt.executeUpdate("Insert into Propositions values (5,'Autre choix',False, False);") ; //effectue une validation des commandes opérées i = stmt.executeUpdate("commit;") ; //exécute la requête sélection et stocke le résultat dans une variable Resultset ResultSet rs = stmt.executeQuery("Select * from propositions"); //positionne le ResultSet sur la première ligne rs.next() ; //récupère le champ numéro 0 (Identifiant de type numérique) int leNum = rs.getInt(0) ; //récupère le champ Libelle String leLibel = rs.getString("propLibelle") ; //-----------------Exemple d'utilisation pour une interface---------------------- //Manipule un objet JcheckBox (case à cocher) de nom ivjchkRep ivjchkRep.setText(leLibel); //affecte le libellé avec la données de la base ivjchkRep.setSelected(monRecordSet.getBoolean("propReponse")); //idem pour la propriété Reponse //------------ //passe à l'enregistrement suivant et manipule un autre bouton rs.next(); String s = rs.getString("propLibelle"); ivjchkAttendu.setText(s); ivjchkAttendu.setSelected(monRecordSet.getBoolean("propValide")); === PARCOURS D'UN RESULTSET === Lorsque l'on souhaite traiter l'intégralité des données d'un resultset, on utilisera la boucle suivante : ResultSet rs = stmt.executeQuery("Select * from propositions"); While rs.next() { …… String libellé = rs.getString("propLibelle") //donne le contenu du champ String propLibelle …. } === PRINCIPALES MÉTHODES ASSOCIÉES À L'OBJET RESULTSET === __Descriptif __ ^ type_retourné ^ nom_méthode(paramètres) ^ Explications ^ |Accès aux champs de l'enregistrement||| |Récupère la valeur d'une colonne dans l'enregistrement courant au format :||| |boolean |getBoolean(int NuméroColonne) |booléen | |boolean |getBoolean(String NomColonne) |booléen | |double| getDouble(int NuméroColonne) |double | |double| getDouble(String NomColonne) |double | |float| getFloat(int NuméroColonne) |float | |float| getFloat(String NomColonne) |float | |int |getInt(int NuméroColonne) |int | |int |getInt(String NomColonne) |int | |long |getLong(int NuméroColonne) |long | |long |getLong(String NomColonne) |long | |Object |getObject(int NuméroColonne) | Object | Descriptif ^ type_retourné ^ nom_méthode(paramètres) ^ Explications ^ |Object |getObject(String NomColonne) |Object | |short |vgetShort(int NuméroColonne) |short | |short |vgetShort(String NomColonne) |short | |String |getString(int NuméroColonne) |String | |String |getString(String NomColonne) |String| |Accès aux champs de l'enregistrement ||| |Récupère la valeur d'une colonne dans l'enregistrement courant en tant qu'objet||| |Date| getDate(int NuméroColonne) |java.sql.Date| |Date |getDate(int NuméroColonne, Calendar cal) |java.sql.Date| |Date |getDate(String NomColonne) |java.sql.Date| |Date |getDate(String NomColonne, Calendar cal) |java.sql.Date| |Time |getTime(int NuméroColonne) |java.sql.Time| |Time |getTime(int NuméroColonne, Calendar cal) |java.sql.Time| |Time |getTime(String NomColonne) |java.sql.Time| |Time |getTime(String NomColonne, Calendar cal) |java.sql.Time| |Déplacements et positionnements||| |boolean |isAfterLast() || |boolean |isBeforeFirst() || |boolean |isFirst() || |boolean |isLast() || |boolean |last() || |Boolean |next() || |boolean |previous() || |int |getRow() |Indique le numéro d'enregistrement courant| |boolean |relative(int rows) |Déplace le curseur d'un certain nombre de lignes en avant (rows > 0) ou en arrière (rows <0)| |void |refreshRow() | Rafraîchit l'enregistrement courant| |void |updateRow() |JDBC 2.0 met à jour la base de données avec le nouveau contenu de l'enregistrement courant| |Tests et Requête||| |Statement |getStatement() |JDBC 2.0 retourne la requête qui a produit cet objet Resultset| |boolean |rowDeleted() |JDBC 2.0 Indique si un enregistrement a été effacé| |boolean |rowInserted() |JDBC 2.0 Indique si l'enregistrement courant a été inseré| |boolean |rowUpdated() |JDBC 2.0 Indique si l'enregistrement courant a été mis à jour.| ===== 3 L'OBJET RESULTSETMETADATA ===== Il s'agit de pouvoir récupérer des informations sur la structure de la table associée à un resultSet : nom et type des champs, nombre de champs par exemple. __**Exemple d'utilisation :**__ Statement stmt = con.createStatement(); ResultSet rs=stmt.executeQuery("select * from proposition"); ResultSetMetaData metaRS = rs.getMetaData(); int nbCols = metaRS.getColumnCount(); String nomChamps[ ] = new String [nbCols] ; //-------------------------------------------------------------- ….. for (int i=1; i<=nbCols;i++) { nomChamps[i]=rsmd.getColumnName(i) } === Principales méthodes associées à l'objet ResultSetMetaData === __Descriptif__ ^ type_retourné ^ nom_méthode(paramètres) ^ Explications ^ |String |getColumnClassName(int column) |Retourne le nom de la classe dont la colonne est une instance| |int |getColumnCount() |Retourne le nombre de colonne dans le resultset| |int |getColumnDisplaySize(int column) |Donne la taille maximum de la colonne en nombre de caractère| |String |getColumnLabel(int column) |Donne le nom d'affichage de la colonne| |String |getColumnName(int column) |Retourne le nom de la colonne désignée| |int |getColumnType(int column) |Donne le type SQL de la colonne (CHAR, INTEGER…)| |String |getColumnTypeName(int column) |Donne le type de la colonne dans l'application SGBD| |int |getPrecision(int column) |Donne le nombre de chiffres de la colonne| |int |getScale(int column) |Donne le nombre de chiffre après la virgule de la colonne| |String |getTableName(int column) |Gets the designated column's table namev |Boolean |isAutoIncrement(int column) |Indique si la colonne est une numérotation automatique (auquel cas elle sera en lecture seule)| |Boolean |isCaseSensitive(int column) |Indique si la colonne est sensible à la casse| |Boolean |isCurrency(int column) |Indique si la colonne est de type monétaire| |Boolean |isDefinitelyWritable(int column) |Indique si l'écriture est possible et assurée sur cette colonne| |int |isNullable(int column) |Indique si la colonne accepte les valeurs nulles| |Boolean |isReadOnly(int column) |Indique si la colonne est en lecture seule| |Boolean |isSearchable(int column) |Indique si la colonne peut être utilisée dans une clause Where| |Boolean |isSigned(int column) |Indique si la colonne est en numérique signée (positif et négatif)| |Boolean |isWritable(int column) |Indique s'il est possible d'écrire dans la colonne| ===== III PERSISTANCE ===== En natif dans la technologie, Sun propose la possibilité de faire durer les objets au-delà de la mémoire de la machine qui l’exécute. Une implémentation de la classe Serializable permettra d’enregistrer les instances d’objet dans un flux (fichier ou réseau). Soit la classe Enchere décrite comme suit : public class Enchere implements Serializable {…….} Soit une classe ayant pour propriété : Enchere tabEncheres ; L’enregistrement de l’ensemble des données du tableau tabEncheres peut être réalisé par la fonction : public void externalise() {//try et catch pour prendre en compte la gestion d’erreurs try {ObjectOutputStream os ; //pour effectuer la sortie os = new ObjectOutputStream (new FileOutputStream("c:\\essai.sortie")); //génère un fichier, attention au double \ for (i=1;i<=max; i++) { os.writeObject(tabE[]); } os.close(); } catch (Exception e) {e.printStackTrace();} Le code pour permettre la récupération aura la forme : try{ ObjectInputStream is = new ObjectInputStream (new FileInputStream("c:\\essai.sortie")); //déclare et ouvre le fichier en lecture for (i=1;i<=max; i++) { tabEncheres[i]= (Enchere) is.readObject(); //on retransforme (parse) l’information lue au format de l’objet instancié }} catch (Exception e) {e.printStackTrace();}