Introduction à JDBC
Transcription
Introduction à JDBC
Département Informatique Programmation en Java Laboratoire de base de données Introduction à JDBC par Louis SWINNEN Ce document est disponible sous licence Creative Commons indiquant qu’il peut être reproduit, distribué et communiqué pour autant que le nom de l’auteur reste présent, qu’aucune utilisation commerciale ne soit faite à partir de celui-ci et que le document ne soit ni modifié, ni transformé, ni adapté. http://creativecommons.org/licenses/by-nc-nd/2.0/be/ La Haute Ecole Libre Mosane (HELMo) attache une grande importance au respect des droits d’auteur. C’est la raison pour laquelle nous invitons les auteurs dont une oeuvre aurait été, malgré tous nos efforts, reproduite sans autorisation suffisante, à contacter immédiatement le service juridique de la Haute Ecole afin de pouvoir régulariser la situation au mieux. Avril 2009 Laboratoire de Base de données Introduction à JDBC par Louis SWINNEN 1. Introduction Comme nous vous demandons d’explorer JDBC personnellement, ce document explique les notions importantes et donne des références vers la documentation. Ce document détaille également l’installation des gestionnaires JDBC sur une machine (votre portable, votre ordinateur personnel, …). 1.1 JDBC de quoi s’agit-il ? JDBC (Java DataBase Connectivity) est un ensemble logiciel qui permet à une application Java d’établir une connexion vers une base de données. Les différents éléments de cet ensemble logiciel sont décrits à la figure 1. Figure 1 : architecture JDBC1 [1] Au sommet, nous trouvons l’application Java qui souhaite communiquer avec une base de données. Cette application utilise l’API JDBC (ensemble de classes et de méthodes spécifiques) pour dialoguer avec la base de données. Ces classes et méthodes sont identiques à toutes les bases de données. L’API JDBC utilise un driver ou gestionnaire JDBC pour établir la connexion avec la base de données. Ce gestionnaire est spécifique au SGBD auquel on souhaite se connecter. Il est, par ailleurs, fournit par le constructeur de la base de données. Il en existe de plusieurs types et aujourd’hui ils sont très souvent euxmêmes écrit en Java ce qui les rend portable également 1 Figure : Copyright 1994-2007 Sun Microsystems, Inc. All Rights Reserved © L. Swinnen, 2009 2 1.2 Les gestionnaires JDBC Dans le cadre de nos laboratoires, deux systèmes de gestion de base de données sont principalement utilisés, à savoir Oracle Express Edition et SQL Server 2005. Pour ces deux SGBD, des drivers JDBC sont disponibles. Oracle Express Edition : MS SQL Server 2005 : http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html http://msdn.microsoft.com/en-us/data/aa937724.aspx Tableau 1 : gestionnaires JDBC Le tableau 1 détaille les liens à partir desquels les différents gestionnaires JDBC peuvent être téléchargés. Pour Oracle, veillez bien à choisir le gestionnaire SQLJ/JDBC 10g Release 2 (version 10.2.xx). Il faut, au moins, télécharger les composants nommés ojdbc14.jar et orai18n.jar. Pour SQL Server, vous pouvez télécharger la version 1.2 du gestionnaire JDBC pour SQL Server 2005. Vous trouverez également l’ensemble des gestionnaires JDBC sur chaque machine dans le répertoire C:\Program files\JDBC. 1.3 Installation des gestionnaires JDBC La procédure d’installation consiste à placer les gestionnaires JDBC dans un dossier de votre choix et ensuite de référencer ces gestionnaires. Les gestionnaires JDBC prennent la forme de fichiers Jar. Un fichier Jar est une archive Java pouvant contenir des composants (des classes compilées directement utilisables). Dans votre environnement de développement (par exemple sous NetBeans, Eclipse ou IntelliJ), il faut indiquer, dans votre projet, le lien vers le gestionnaire JDBC à utiliser. • Par exemple dans NetBeans, il faut ajouter les fichiers JAR dans votre projet. Pour ce faire, dans la section Libraries (panneau de gauche), faire un clic-droit et choisir Add Jar/Folders et sélectionner les fichiers JAR nécessaires à votre application. • Sous Eclipse, il est possible lors de la création du projet Java d’indiquer des libraires particulières. Ainsi dans la fenêtre Java Settings, choisir l’onglet Libraries et ensuite Add External JARs et sélectionner les fichiers JAR. Si le projet est déjà créé, il est possible d’ajouter les libraires en faisant un clic-droit sur le nom du projet et en choisissant Properties. Ensuite, l’option Java Build Path fait apparaître une série d’onglets. Choisir dans l’onglet Librairies l’option Add External JARs et sélectionner les fichiers JARs à intégrer. • Sous IntelliJ, il faut ajouter également les gestionnaires JDBC. Pour ce faire, dans le projet (panneau de gauche), faire un clic-droit sur Libraries et choisir Jump To Source. Les paramètres du projet sont alors affichés, choisir dans la section Librairies, l’icône + au sommet. Ensuite, il faut donner un nom à ces librairies, comme par exemple JDBC Oracle et enfin, dans le panneau de droite, choisir l’option Attach Classes et choisir les librairies à ajouter. En dehors de l’environnement de développement, il faut modifier la variable d’environnement CLASSPATH afin que celle-ci pointe vers les fichiers JAR. Ainsi, sous Windows, il faut effectuer les étapes suivantes (en administrateur) : • Sous Windows XP : Clic-droit sur le poste de travail, choisir Propriétés puis l’onglet Avancé et cliquer sur Variables d’environnement. © L. Swinnen, 2009 3 Sous Windows Vista : Clic-droit sur Ordinateur puis choisir Propriétés puis, dans les tâches qui apparaissent à gauche, choisir Paramètres système avancés et ensuite cliquer sur Variables d’environnement. • Dans la section Variables système, localiser la variable PATH (qui indique le chemin de recherche par défaut) et modifiez celle-ci pour ajouter le chemin vers les fichiers systèmes Java. Ainsi, si votre compilateur Java est installé dans C:\Program Files\Java\jdk1.6.0_06, ajoutez ceci à la fin de la valeur existante : ;C:\Program Files\Java\jdk1.6.0_06\bin • Egalement dans la section Variables système, vérifiez si la variable CLASSPATH est définie. Si ce n’est pas le cas, il faut créer une nouvelle variable, sinon modifier la variable existante. Il faut ici ajouter les chemins vers les différents gestionnaires JDBC installés sur votre machine. Si, comme à l’école, les gestionnaires sont installés dans un répertoire JDBC dans le dossier Program Files, la variable CLASSPATH doit contenir notamment le chemin suivant : C:\Program Files\JDBC\Microsoft SQL Server 2005 JDBC Driver\sqljdbc_1.1\fra\sqljdbc.jar ; C:\Program Files\JDBC\Oracle\ojdbc14.jar; C:\program files\JDBC\Oracle\orai18n.jar Sous les systèmes basés Unix, il faut réaliser les mêmes modifications. Par exemple, sous Linux, si vous avez installé le compilateur Java et les gestionnaires JDBC dans le répertoire /opt, vous devez adapter la variable PATH et CLASSPATH comme sous Windows. Le plus simple est d’ajouter le fichier suivant dans le répertoire /etc/profile.d : Fichier /etc/profile.d/java.sh #! /bin/bash JAVA_HOME=/opt/jdk1.6.0_06 PATH=$PATH:$JAVA_HOME/bin CLASSPATH=$CLASSPATH:/opt/JDBC/Microsoft\ SQL\ Server\ 2005\ JDBC\ Driver/ sqljdbc_1.1/fra/sqljdbc.jar:/opt/JDBC/Oracle/odjbc14.jar:/opt/JDBC/Oracle/ orai18n.jar export JAVA_HOME PATH CLASSPATH Il reste ensuite à rendre ce script exécutable soit en ajoutant la permission execute par l’interface graphique, ou en entrant dans un terminal la commande suivante : chmod +x /etc/profile.d/java.sh Une fois cette modification effectuée, il suffit de redémarrer la machine et l’environnement Java est prêt à être utilisé. © L. Swinnen, 2009 4 2. Utilisation de JDBC Afin d’illustrer l’utilisation de JDBC, je propose un exemple qui sera décrit et détaillé par la suite. Nous allons donc commencer par décrire la base de données considérée et puis nous détaillerons les éléments à mettre en place au travers d’un exemple. Enfin nous reviendrons sur quelques concepts théoriques importants dans toutes applications JDBC. Nos exemples se concentreront autour des deux SGBD utilisés à l’école, à savoir SQL Server et Oracle. Si vous souhaitez établir des connexions vers d’autres bases de données, il est important, après avoir installé le gestionnaire de base de données de bien lire la documentation afin de déterminer comment la connexion peut être établie. 2.1 La base de données considérée Client NCLI NOM ADRESSE LOCALITE CAT COMPTE id: NCLI acc Commande NCOM DATECOM NCLI id: NCOM acc ref: NCLI acc Lignecom NCOM NPRO QCOM id: NPRO NCOM acc ref: NPRO ref: NCOM acc Produit NPRO LIBELLE PRIX QSTOCK id: NPRO acc 2.2 Exemple d’implémentation de connexion vers Oracle Un élément important dans la mise en place d’une application JDBC est la chaîne de connexion. En effet, JDBC utilise une URL pour localiser le serveur de base de données. Cette URL est propre au SGBD et mentionne généralement : le nom (ou l’IP) du serveur de base de données, le nom de la base de données, le nom de l’utilisateur et le mot de passe de connexion. Dans le cas d’Oracle XE (version utilisée et installée à l’école), la chaîne de connexion est la suivante : jdbc:oracle:thin:username/pass@//serverName:1521/XE La chaîne de connexion doit préciser : • serverName : Le nom / IP du serveur Oracle sur lequel on se connecte • databaseName : Le nom de la base de donnée doit être XE • userName : le nom de l’utilisateur pour la connexion • pass : le mot de passe de connexion En plus de la chaîne de connexion, il est nécessaire de mentionner à la couche JDBC le chemin vers le pilote utilisé. Ce chemin est propre à chaque pilote JDBC. Ainsi, pour Oracle XE, il faut mentionner comme chemin : oracle.jdbc.OracleDriver Ce chemin sera présent dans l’instruction « Class.forName » utilisé lors de la connexion. 2.3 Exemple d’implémentation de connexion vers SQL Server Un élément important dans la mise en place d’une application JDBC est la chaîne de connexion. En effet, JDBC utilise une URL pour localiser le serveur de base de données. Cette URL est propre au SGBD et mentionne généralement : le nom (ou l’IP) du serveur de base de données, le nom de la base de données, le nom de l’utilisateur et le mot de passe de connexion. © L. Swinnen, 2009 5 Dans le cas de SQL Serveur 2005 (version utilisée et installée à l’école), la chaîne de connexion est la suivante : jdbc:sqlserver://serverName:1433;databaseName=nomBD;user=username ;password=pass La chaîne de connexion doit préciser : • serverName : Le nom / IP du serveur SQL Server sur lequel on se connecte • databaseName : le nom de la base de données à utiliser • user : le nom de connexion • password : le mot de passe associé pour la connexion En plus de la chaîne de connexion, il est nécessaire de mentionner à la couche JDBC le chemin vers le pilote utilisé. Ce chemin est propre à chaque pilote JDBC. Ainsi, pour SQL Server 2005, il faut mentionner comme chemin : com.microsoft.sqlserver.jdbc.SQLServerDriver Ce chemin sera présent dans l’instruction « Class.forName » utilisé lors de la connexion. 2.4 Schéma classique de connexion Le schéma présenté ci-dessous (voir code 1) permet la connexion au SGBD en utilisant la couche JDBC. Il faut bien sûr indiquer les éléments propres à votre environnement comme la chaîne de connexion ou le chemin vers le pilote. import java.sql.*; . . . try { Class.forName("<chemin vers pilote>") ; String connectString = "<chaine de connexion>"; Connection conn = DriverManager.getConnection(connectString); . . . } catch(ClassNotFoundException ex) { System.out.println("Pilote JDBC non trouve"); } catch(SQLException ex) { ex.printStackTrace(); } code 1 : Schéma de connexion à la base de données Le code 1 montre le schéma de connexion. Ainsi, l’instruction Class.forName permet de charger le pilote SGBD. Si celui-ci n’est pas trouvé, une erreur de type ClassNotFoundException est levée. type Connection est obtenu via la méthode statique DriverManager.getConnection(). Grâce à l’objet Connection, il sera possible d’exécuter des requêtes SQL sur le SGBD. Ensuite, un objet de Deux types de requêtes sont possibles : des requêtes retournant des résultats (principalement des requêtes de sélection) et des requêtes ne retournant aucun résultat (principalement des requêtes d’ajout, de mise à jour ou de suppression). © L. Swinnen, 2009 6 2.5 Requête de sélection Dans ce paragraphe, nous allons aborder les requêtes retournant des résultats. Le schéma d’exécution est présenté ci-dessous (voir code 2). import java.sql.*; . . . try { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT NCLI, NOM, CAT, COMPTE FROM CLIENT"); while(rs.next()) { String ncli = rs.getString("NCLI"); String nom = rs.getString("NOM"); String cat = rs.getString("CAT"); float compte= rs.getFloat("COMPTE"); . . . } rs.close(); stmt.close(); } catch(SQLException ex) { ex.printStackTrace(); } code 2 : exemple de sélection Dans le code 2, nous avons un exemple de sélection dans la table client. Ainsi, nous effectuons la requête select ncli, nom, cat, compte from client. Cette requête risque de retourner plusieurs lignes. Afin de gérer ces lignes séparément, l’objet ResultSet est utilisé. Il s’agit d’un objet particulier permettant de traiter le résultat de la requête. Grâce à rs.next(), il est possible de parcourir les résultats. Cet appel retourne true s’il y a encore un résultat à traiter ou false sinon. Afin d’avoir accès aux éléments sélectionnés par la requête exécutée, nous avons, dans l’objet ResultSet, bons nombres de méthodes. Ainsi, comme montré dans l’exemple ci-dessus, la méthode getString qui prend en paramètre le nom de la colonne voulue permet d’obtenir, sous la forme d’une chaîne de caractère, la valeur de cette colonne. A coté de la méthode getString, il existe bien d’autres méthodes permettant d’obtenir une valeur de colonne sous divers format. Ainsi, getFloat permet de récupérer la valeur d’une colonne sous la forme d’un réel. Il faut bien sûr respecter le schéma de la base de données afin de pouvoir réaliser les conversions. En cas d’erreur lors de l’exécution de la requête, une exception de type SQLException est levée. Il est primordial de capturer et traiter cette exception. Une fois l’exécution terminée, il convient de terminer par l’appel aux méthodes close(). Comme déjà dit, nous éviterons toujours des requêtes de type select * from puisque ce type de requête empêche l’évolution du schéma de la base de données. De plus, il serait probablement bon d’accéder aux informations contenues dans la base de données depuis des vues. Ainsi, le schéma de la base de données peut évoluer indépendamment de l’application. © L. Swinnen, 2009 7 2.6 Requête de mise à jour Dans ce paragraphe, nous allons aborder les requêtes ne retournant aucun résultat, il s’agit principalement des requêtes de mise à jour (INSERT, UPDATE, DELETE). Le schéma d’exécution est présenté ci-dessous (voir code 3). import java.sql.*; . . . try { Statement stmt = conn.createStatement(); stmt.executeUpdate("UPDATE Produit SET QSTOCK = 3 WHERE NPRO = ABC123"); . . . stmt.close(); } catch(SQLException ex) { ex.printStackTrace(); } code 3 : exemple de mise à jour Dans le code 3, nous avons un exemple de mise à jour au moyen d’une requête UPDATE. La méthode executeUpdate retourne un entier représentant le nombre de lignes altérées par la mise à jour. Grâce à cette information, il est possible de vérifier si la mise à jour a bien eu lieue. Comme précédemment, en cas d’erreur, une exception SQLException est levée et il convient de la capturer et la gérer. Une fois la fin du traitement atteint, il convient de terminer l’exécution grâce à la méthode close(). 2.7 L’utilisation des requêtes préparées Il est très commode, lors de la rédaction du code, d’utiliser des requêtes préparées et de garnir les différents éléments en fonction des besoins. Cette façon de procéder simplifie grandement l’écriture du code et assure une certaine portabilité. En effet, lors de la migration vers un autre SGBD, il est nécessaire de vérifier chaque requête afin de s’assurer que la syntaxe demeure correcte pour le nouveau SGBD. Or, le format des dates par exemple, peut amener des problèmes : en effet, ce dernier peut varier d’un SGBD à l’autre. Dès lors, l’utilisation de requêtes préparées évite ces problèmes et rend la migration vers un autre SGBD plus aisé. Le code 4 montre un exemple de mise à jour au moyen de requêtes préparées. © L. Swinnen, 2009 8 import java.sql.*; . . . try { PreparedStatement pst = conn.prepareStatement( "INSERT INTO COMMANDE (NCOM, NCLI, DATECOM) VALUES(?,?,?)"); pst.setString(1, "C010"); // set NCOM pst.setString(2, "CL01"); // set NCLI SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy"); pst.setDate(3, sdf.parse("20/02/08", new ParsePosition(0)); pst.executeUpdate(); pst.close(); } catch(SQLException ex) { ex.printStackTrace(); } code 4 : exemple de requête préparée Une requête préparée prend la forme d’un objet PreparedStatement. On remarque que la requête contient plusieurs caractères « ? » représentant des paramètres. Grâce aux méthodes set utilisées sur l’objet PreparedStatement, ces caractères seront remplacés par des valeurs. Ainsi, la ligne mentionnant pst.setString(1, "C010") remplace le 1ier caractère « ? » par la valeur C010. Ensuite, le second paramètre est remplacé par la valeur CL01 et le dernier par la date du 20 février 2008. Une fois tous les paramètres garnis, il reste à exécuter la requête. Il suffit dès lors d’appeler la méthode executeUpdate sur cet objet pour lancer cette exécution. Nous remarquons immédiatement que même en cas de changement de SGBD, le code cidessus reste valide. Cela n’aurait pas été nécessairement le cas si nous avions construit à la main la chaîne à exécuter. Bien sûr, les requêtes préparées peuvent également être utilisées dans des requêtes de sélection. Ainsi, au lieu d’exécuter la méthode executeUpdate, il convient d’appeler la méthode executeQuery qui retourne un ResultSet qu’il est ensuite possible de parcourir, comme vu précédemment. 2.8 Les transactions Lorsqu’il est nécessaire de regrouper plusieurs requêtes SQL au sein d’une transaction (afin de profiter des propriétés ACID – Atomicité, Cohérence, Isolation, Durabilité), il faut empêcher le SGBD de traiter chaque requête indépendamment. Nous allons examiner le code 5 qui illustre le concept de transaction en Java. © L. Swinnen, 2009 9 import java.sql.*; . . . try { conn.setAutoCommit(false); // requete 1 Statement stmt1 = conn.createStatement(); stmt1.executeUpdate(...); . . . // requete 2 Statement stmt2 = conn.createStatement(); stmt2.executeUpdate(...); stmt1.close(); stmt2.close(); conn.commit(); conn.setAutoCommit(true); } catch(SQLException ex1) { try { conn.rollback(); conn.setAutoCommit(true); } catch(SQLException ex2) { /* nothing to do */ } ex1.printStackTrace(); } code 5 : exemple de transaction Comme le montre le code 5, la création de la transaction commence par empêcher les commandes commit implicites réalisées automatiquement par le SGBD grâce à l’appel de la méthode setAutoCommit(false). Désormais, les instructions commit devront être réalisées manuellement. Ensuite, les différentes requêtes appartenant à la transaction sont exécutées. Enfin, au terme de la transaction, il faut accepter les modifications au moyen de l’appel à la méthode commit(). Enfin, il convient de revenir à l’état initial en demandant au SGBD de réaliser automatiquement les commit après chaque requête via l’exécution de setAutoCommit(true). Attention ! Comme le montre le code 5, il convient de compléter la gestion des erreurs pour appeler la méthode rollback() (annulation de toutes les modifications consécutives à la transaction) et retourner à l’état initial au moyen de setAutoCommit(true). Comme l’exécution de ces méthodes peut entraîner une exception de type SQLException, nous voyons qu’un nouveau bloc try … catch est nécessaire dans le bloc catch précédent. © L. Swinnen, 2009 10 3. Quelques erreurs courantes 3.1 Séparation des couches Il est important de bien séparer les couches logiques de votre programme. Ainsi, la couche accès BD est la seule à utiliser la couche JDBC et accéder à la base de données. Il faut donc prendre l’habitude de : • masquer et traiter les erreurs de type SQLException car celles-ci ne devraient jamais être transmises à une autre couche o vous pouvez choisir de renvoyer un état ou une exception que vous avez créée vous même. o Evitez les blocs try … catch dont l’exception capturée est Exception et dont le contenu est vide, cela montre clairement que votre programmation n’est pas achevée • retourner les informations sous la forme de données de l’application. Ne retournez donc pas de ResultSet ou autre chose provenant de JDBC. Vous pouvez : o Utilisez un conteneur pour assembler les données (liste, map, set) o Modéliser directement votre information. Par exemple, retourner un client ou un produit. Il faut pour cela définir l’objet Modèle et garnir les données en fonction de ce qui est observé dans la base de données. C’est probablement la manière la plus propre. Ainsi, vous concentrez les informations et requêtes SQL au niveau de la couche accès BD uniquement. 3.2 Indépendance par rapport au modèle Prenez également l’habitude d’être indépendant du SGBD. Pour ce faire, il convient de : • Eviter à tout prix les requêtes de type select * from, car en cas de modification du schéma, votre couche accès BD doit être adaptée. • Accéder à la base de données au travers de vues. C’est une autre façon de « prendre de la distance » par rapport au schéma de la BD (pour autant que votre vue n’utilise pas un select * from). 3.3 Découpe selon les fonctionnalités Il arrive également très souvent que les étudiants construisent les méthodes de la couche accès BD en fonction des requêtes et non des fonctionnalités. Ainsi, • Il est plus judicieux de prévoir des fonctionnalités complètes : ajout d’une commande par exemple qui peut comporter plusieurs requêtes : vérifier l’état du stock, enregistrer les lignes de commande et enregistrer la commande. • Lorsqu’on découpe en fonctionnalité, il est également nécessaire de prévoir des transactions pour s’assurer que l’opération est effectuée complètement, ou pas du tout. Pour rappel, une transaction en JDBC débute avec l’instruction setAutoCommit(false) et se termine par un commit ou un rollback suivi de l’instruction setAutoCommit(true). o En conséquence, il ne faut pas utiliser BEGIN TRANSACTION et END TRANSACTION ! © L. Swinnen, 2009 11 4. Exemple d’une couche d’accès BD Supposons que nous devions construire une application JAVA capable de réaliser l’enregistrement d’une nouvelle commande, supprimer une commande existante, consulter les commandes d’un client et permettre l’approvisionnement du stock. Ces différentes fonctionnalités doivent être programmées au niveau de la couche accès BD. Ainsi, l’enregistrement d’une nouvelle commande doit : 1. Créer un nouvel enregistrement dans la table commande pour le client concerné 2. Créer les lignes de commande en fonction des demandes du client 3. Vérifier que le stock actuel peut satisfaire la commande, sinon la rejeter. La suppression de la commande existante doit : 1. Supprimer toutes les lignes de commandes liées à cette commande 2. Supprimer la commande concernée La consultation des commandes d’un client doit permettre d’obtenir toutes les commandes de ce client. Il faut, en outre, obtenir les détails (i.e. les lignes de commande) en relation avec ces commandes. L’approvisionnement du stock permet de mettre à jour le stock d’un produit donné. Comme nous le voyons, toutes ces fonctionnalités entraînent la présence de transaction afin d’assurer l’atomicité et la cohérence de la base de données. L’exemple ci-dessous comprend à la fois la couche accès BD qui contient les requêtes SQL et la programmation JDBC mais également les objets modèles comme Commande, Produit, Client, LigneCommande. Enfin, un programme de test est également donné en guise d’exemple. Il s’agit bien sûr d’un exemple d’implantation et non d’une quelconque démarche à suivre. Ainsi, cet exemple pourrait être amélioré par l’utilisation d’exception particulière afin de signaler le type d’erreur rencontré, réaliser davantage de vérification des données reçues et obtenue de la base de données, exploiter des vues plutôt que les tables directement. Je laisse le soin au lecteur de corriger et améliorer ce code. © L. Swinnen, 2009 12 fichier BDAccess.java (couche accès BD) package BD; import BO.Commande; import BO.LigneCommande; import BO.Produit; import java.sql.*; import java.util.List; import java.util.ArrayList; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 21:27:37 */ public class BDAccess { public static final String[] CONN_STR = { "jdbc:sqlserver://<serverName>:1433;databaseName=<dbName>;user=<userName>;password=<passwd>", "jdbc:oracle:thin:<userName>/<passwd>@//<serverName>:1521/<dbName>"}; public static final String[] CLASS_STR = { "com.microsoft.sqlserver.jdbc.SQLServerDriver", "oracle.jdbc.OracleDriver"}; public static final int MSSQL = 0; public static final int ORACLE = 1; private String serverName; private String dbName; private String userName; private String password; private boolean isConnected = false; private Connection dbConnection = null; public BDAccess() { } /* default contructor */ public BDAccess(String _serverName, String _dbName, String _userName, String _pass) { serverName = _serverName; dbName = _dbName; userName = _userName; password = _pass; } public boolean connect(int _dbType) { try { Class.forName(CLASS_STR[_dbType]); String connStr = CONN_STR[_dbType].replaceAll("<serverName>", serverName).replaceAll("<dbName>", dbName).replaceAll("<userName>", userName).replaceAll("<passwd>", password); dbConnection = DriverManager.getConnection(connStr); isConnected = true; return true; } catch(ClassNotFoundException ex) { ex.printStackTrace(); return false; } catch(SQLException ex) { ex.printStackTrace(); return false; } catch(ArrayIndexOutOfBoundsException ex) { System.out.println("[BDAccess][connect] dbType not supported"); return false; } } public boolean disconnect() { if(isConnected) { try { dbConnection.close(); return true; } catch(SQLException ex) { ex.printStackTrace(); return false; } } else return true; } public boolean ajouteCommande(Commande _cmd) { Statement stmt; PreparedStatement pst; © L. Swinnen, 2009 13 ResultSet rs; if(! isConnected) return false; try { /* *** 1. Start transaction *** */ dbConnection.setAutoCommit(false); /* *** 2. CREATE COMMANDE *** */ pst = dbConnection.prepareStatement("INSERT INTO commande(NCOM, NCLI, DATECOM) VALUES (?,?,?)"); pst.setString(1, _cmd.getNumeroCommande()); pst.setString(2, _cmd.getNumeroClient()); pst.setDate(3, new java.sql.Date(_cmd.getDateCommande().getTime())); pst.executeUpdate(); pst.close(); /* *** 3. CREATE LIGNECOM AND DECREASE STOCK *** */ for(int i=0; i<_cmd.getNombreLigne(); ++i) { LigneCommande lc = _cmd.getLigneCommande(i); /* *** 3a. DECREASE STOCK *** */ stmt = dbConnection.createStatement(); rs = stmt.executeQuery("SELECT QSTOCK FROM Produit WHERE NPRO = '" + lc.getNumeroProduit()+"'"); if(rs.next()) { /* only 1 row because select query on primary key */ int qte = rs.getInt("QSTOCK"); if(qte - lc.getQteCommandee() < 0) { /* stock is too low -> aborting */ dbConnection.rollback(); dbConnection.setAutoCommit(true); return false; } rs.close(); stmt.close(); stmt = dbConnection.createStatement(); stmt.executeUpdate("UPDATE Produit SET QSTOCK=" + (qte – lc.getQteCommandee()) + " WHERE NPRO='" + lc.getNumeroProduit()+"'"); } else { /* no row ? aborting ! */ dbConnection.rollback(); dbConnection.setAutoCommit(true); return false; } /* *** 3b. CREATE LIGNECOMMANDE *** */ stmt = dbConnection.createStatement(); stmt.executeUpdate("INSERT INTO LIGNECOM(NCOM, NPRO, QCOM) VALUES ('" + lc.getNumeroCommande() + "','"+ lc.getNumeroProduit() + "',"+lc.getQteCommandee()+ ")"); stmt.close(); } /* *** 4. End of Transaction *** */ dbConnection.commit(); dbConnection.setAutoCommit(true); return true; } catch(SQLException ex) { ex.printStackTrace(); try { dbConnection.rollback(); dbConnection.setAutoCommit(true); } catch (SQLException e) { } return false; } } public boolean supprimeCommande(Commande _cmd) { PreparedStatement pst; Statement stmt; if(!isConnected) return false; try { © L. Swinnen, 2009 14 /* *** 1. Start transaction *** */ dbConnection.setAutoCommit(false); /* *** 2. Delete ALL lignecom from this commande *** */ pst = dbConnection.prepareStatement("DELETE FROM LIGNECOM WHERE NCOM=?"); pst.setString(1, _cmd.getNumeroCommande()); pst.executeUpdate(); pst.close(); /* *** 3. Delete requested commande *** */ pst = dbConnection.prepareStatement("DELETE FROM COMMANDE WHERE NCOM=?"); pst.setString(1, _cmd.getNumeroCommande()); pst.executeUpdate(); pst.close(); /* *** 4. End of Transaction *** */ dbConnection.commit(); dbConnection.setAutoCommit(true); return true; } catch(SQLException ex) { ex.printStackTrace(); try { dbConnection.rollback(); dbConnection.setAutoCommit(true); } catch (SQLException e) { } return false; } } public Produit obtenirProduit(String _npro) { Produit p = new Produit(); PreparedStatement pst; ResultSet rs; try { pst = dbConnection.prepareStatement("SELECT NPRO, LIBELLE, PRIX, QSTOCK FROM Produit WHERE NPRO=?"); pst.setString(1, _npro); rs = pst.executeQuery(); if(rs.next()) { p.setNumero(_npro); p.setLibelle(rs.getString("LIBELLE")); p.setPrix(rs.getInt("PRIX")); p.setStock(rs.getInt("QSTOCK")); return p; } else return null; /* aucun produit trouvé */ } catch(SQLException ex) { ex.printStackTrace(); return null; } } public boolean modifieProduit(Produit _prod) { PreparedStatement pst; if(_prod.getNumero() == null) return false; try { pst = dbConnection.prepareStatement("UPDATE PRODUIT SET LIBELLE=?, PRIX=?, QSTOCK=? WHERE NPRO=?"); pst.setString(1, _prod.getLibelle()); pst.setInt(2, _prod.getPrix()); pst.setInt(3, _prod.getStock()); pst.setString(4, _prod.getNumero()); pst.executeUpdate(); pst.close(); return true; } catch(SQLException ex) { ex.printStackTrace(); return false; } } public List<Commande> obtenirCommandeClient(String _ncli) { List<Commande> liste = new ArrayList<Commande>(); © L. Swinnen, 2009 15 if(_ncli == null) return null; try { /* *** TASK 1: Find all 'Commandes' *** */ ResultSet listeCommande; PreparedStatement pstCommande = dbConnection.prepareStatement("SELECT NCOM, NCLI, DATECOM FROM COMMANDE WHERE NCLI=?"); pstCommande.setString(1, _ncli); listeCommande = pstCommande.executeQuery(); while(listeCommande.next()) { Commande cmd = new Commande(listeCommande.getString("NCOM"), listeCommande.getString("NCLI"), listeCommande.getDate("DATECOM")); /* *** TASK 2: Find all 'LigneCom' for the 'commande' *** */ ResultSet listeLigne; PreparedStatement pstLignecom = dbConnection.prepareStatement("SELECT NCOM, NPRO, QCOM FROM LIGNECOM WHERE NCOM=?"); pstLignecom.setString(1, cmd.getNumeroCommande()); listeLigne = pstLignecom.executeQuery(); while(listeLigne.next()) { cmd.ajouteLigneCommande(listeLigne.getString("NPRO"), listeLigne.getInt("QCOM")); } listeLigne.close(); liste.add(cmd); } listeCommande.close(); return liste; } catch(SQLException ex) { ex.printStackTrace(); return null; } } public public public public public public public public public String getServerName() { return serverName; } String getDBName() { return dbName; } String getUserName() { return userName; } String getPassword() { return password; } boolean isConnected() { return isConnected; } void setServerName(String _serverName) { serverName = _serverName; } void setDBName(String _dbName) { dbName = _dbName; } void setUserName(String _userName) { userName = _userName; } void setPassword(String _pass) { password = _pass; } } © L. Swinnen, 2009 16 fichier Client.java (couche traitement Business Object) package BO; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 23:10:32 */ public class Client { private String numClient; private String nom; private String adresse; private String localite; private String categorie; private float compte; public Client() { } public Client(String _ncli, String _nom, String _adr, String _loc, String _cat, float _cpt ) { numClient = _ncli; nom = _nom; adresse = _adr; localite = _loc; categorie = _cat; compte = _cpt; } public public public public public public public public public public public public String getNumero() { return numClient; } String getNom() { return nom; } String getAdresse() { return adresse; } String getLocalite() { return localite; } String getCategorie() { return categorie; } float getCompte() { return compte; } void setNumero(String _num) { numClient = _num; } void setNom(String _nom) { nom = _nom; } void setAdresse(String _adr) { adresse = _adr; } void setLocalite(String _loc) { localite = _loc; } void setCategorie (String _cat) { categorie = _cat; } void setCompte(float _cpt) { compte = _cpt; } } fichier Produit.java (couche traitement Business Object) package BO; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 23:17:44 */ public class Produit { private String numProduit; private String libelle; private int prix; private int qteStock; public Produit() { } public Produit(String _num, String _lib, int _prix, int _qteStock) { numProduit = _num; libelle = _lib; prix = _prix; qteStock = _qteStock; } public public public public public public public public public String getNumero() { return numProduit; } String getLibelle() { return libelle; } int getPrix() { return prix; } int getStock() { return qteStock; } void setNumero(String _num) { numProduit = _num; } void setLibelle(String _lib) { libelle = _lib; } void setPrix(int _prix) { prix = _prix; } void setStock(int _stock) { qteStock = _stock; } String toString() { return numProduit + ":" + libelle + ":" + prix + ":" + qteStock; } } © L. Swinnen, 2009 17 fichier Commande.java (couche traitement Business Object) package BO; import import import import java.util.Date; java.util.List; java.util.ArrayList; java.util.Iterator; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 23:25:01 */ public class Commande { private String numCommande; private String numClient; private Date dateCommande; private List<LigneCommande> liste = new ArrayList<LigneCommande>(); public Commande() { } public Commande(String _numCommande, String _numClient, Date _dateCommande) { numCommande = _numCommande; numClient = _numClient; dateCommande = _dateCommande; } public void ajouteLigneCommande(LigneCommande _ligneCom) { liste.add(_ligneCom); } public void ajouteLigneCommande(String _numProduit, int _qteCommandee) { liste.add(new LigneCommande(numCommande, _numProduit, _qteCommandee)); } public LigneCommande supprimeLigneCommande(int _index) { if(_index < liste.size()) { return liste.remove(_index); } else return null; } public LigneCommande getLigneCommande(int _index) { if(_index < liste.size()) { return liste.get(_index); } else return null; } public String getNumeroCommande() { return numCommande; } public String getNumeroClient() { return numClient; } public Date getDateCommande() { return dateCommande; } public int getNombreLigne() { return liste.size(); } public void setNumeroCommande(String _num) { numCommande = _num; } public void setNumeroClient(String _num) { numClient = _num; } public void setDateCommande(Date _date) { dateCommande = _date; } public String toString() { String result = numCommande + ":" + numClient + ":"+dateCommande; Iterator<LigneCommande> it = liste.iterator(); while(it.hasNext()) { LigneCommande lc = it.next(); result = result + lc; } return result; } } © L. Swinnen, 2009 18 fichier LigneCommande.java (couche traitement Business Object) package BO; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 23:26:36 */ public class LigneCommande { private String numCommande; private String numProduit; private int qteCommandee; public LigneCommande() { } public LigneCommande(String _numCommande, String _numProduit, int _qte) { numCommande = _numCommande; numProduit = _numProduit; qteCommandee = _qte; } public public public public public public public String getNumeroCommande() { return numCommande; } String getNumeroProduit() { return numProduit; } int getQteCommandee() { return qteCommandee; } void setNumeroCommande(String _num) { numCommande = _num; } void setNumeroProduit(String _num) { numProduit = _num; } void setQteCommandee(int _qte) { qteCommandee = _qte; } String toString() { return "[" + numCommande + ":" + numProduit + ":" + qteCommandee + "]"; } } fichier Programme.java (Programme de test AccesBD) import BD.BDAccess; import BO.Commande; import BO.Produit; import java.text.SimpleDateFormat; import java.text.ParsePosition; /** * Created by IntelliJ IDEA. * User: Louis SWINNEN * Date: 08-avr.-2009 * Time: 22:40:39 */ public class Programme { BDAccess bda; public Programme() { bda = new BDAccess("172.16.130.122", "XE", "clicmd", "clicmd"); // bda = new BDAccess("172.16.130.124", "clicmd", "info", "info"); SQL SERVER // if(bda.connect(BDAccess.MSSQL)) { if(bda.connect(BDAccess.ORACLE)) { System.out.println("Connexion établie"); SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy"); Commande cmd = new Commande("C190", "B112",sdf.parse("09/04/09", new ParsePosition(0))); cmd.ajouteLigneCommande("PA60", 10); cmd.ajouteLigneCommande("PA45", 15); cmd.ajouteLigneCommande("CS264", 100); cmd.ajouteLigneCommande("PS222", 200); Commande cmd2 = new Commande("C190", null, null); if(bda.ajouteCommande(cmd) ) System.out.println("Ajout OK"); else System.out.println("Ajout KO !!"); if(bda.supprimeCommande(cmd2)) System.out.println("Suppression OK"); else System.out.println("Suppression KO !!"); System.out.println(bda.obtenirProduit("CS264")); System.out.println(bda.obtenirCommandeClient("C400")); © L. Swinnen, 2009 19 Produit p = bda.obtenirProduit("PA60 "); p.setStock(p.getStock() + 20); if(bda.modifieProduit(p)) System.out.println("[produit] OK"); else System.out.println("[produit] KO"); bda.disconnect(); } else { System.out.println("Connexion perdue !"); } } public static void main(String[] args) { new Programme(); } } © L. Swinnen, 2009 20 Bibliographie et références [1] [2] [3] [4] JDBC Overview, Sun Microsystem, http://java.sun.com/products/jdbc/overview.html, consulté en juin 2008 Getting Started with JDBC API, Sun Microsystem, http://java.sun.com/j2se/1.5.0/docs/guide/jdbc/getstart/GettingStartedTOC.fm.html, consulté en juin 2008 C. S. Hortsmann et G. Cornell, Au cœur de Java 2 : Notions fondamentales (Vol. 1), CampusPress, Paris, 2004 C. S. Hortsmann et G. Cornell, Au cœur de Java 2 : Fonctions avancées (Vol. 2), CampusPress, Paris, 2004 Information de Copyright OracleTM, SQLJTM, Oracle Express EditionTM are registered trademarks of Oracle Corporation and/or its affiliates. JavaTM, JDBCTM, NetbeansTM are trademarks or registered trademarks of Sun Microsystems, Inc. or its subsidiaries in the United States and other countries. Eclipse © Copyright Eclipse contributors and others 2000, 2009. All rights reserved. Eclipse is a trademark of Eclipse Foundation Inc. IntelliJ IDEA © 2000-2009 JetBrains s.r.o. All rights reserved. Microsoft®, Microsoft SQL Server®, MS SQL Server®, SQL Server® 2005 are trademarks of Microsoft Corporation and/or its affiliates. Tous les noms et marques des produits cités sont des marques déposées par leurs producteurs respectifs. © L. Swinnen, 2009 21