JAVA, JDBC et liaison base de données

5,703 views

Published on

Quelques notions de base pour se connecter à une base de données en Java

1 Comment
5 Likes
Statistics
Notes
No Downloads
Views
Total views
5,703
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
304
Comments
1
Likes
5
Embeds 0
No embeds

No notes for slide

JAVA, JDBC et liaison base de données

  1. 1. Java et bases de données JDBC - Stratégies - Pattern DAO - Hibernate Version du 25/08/2015
  2. 2. JDBC • API orientée objet unifiée d’accès aux SGBD • Soumission de requête via un driver • Accès aux SGBD • Soit par un driver JDBC adapté • Soit par des passerelles • (ex : passerelle JDBC->ODBC)
  3. 3. Process de requétage 1. Chargement du driver 2. Connexion à la base 3. Création de Statement • Envoi de la requête • Avec ou sans préparation 4. Prise en compte du résultat éventuel • Via un ResultSet 5. Fermeture de la connexion
  4. 4. 1 - Chargement du driver import java.sql.*; class Cnx { public Cnx() { try 
 { Class.forName("com.mysql.jdbc.Driver").newInstance(); } catch (ClassNotFoundException e) { System.out.println("Driver spécifié non trouvé"); } }
 } Accès direct au driver par une chaîne de caractères On utilise les prototypes de java.sql
  5. 5. 2 - Connexion à la base Connection cnx; try { cnx=DriverManager.getConnection ("jdbc:mysql://localhost/Bibliotheque","user","passwd"); } catch (SQLException e) { System.out.println("Impossible de se connecter sur la base") ; System.out.println(e) ; }
  6. 6. Comment récupérer la connexion ? • Il faut éviter à tout prix de relancer X fois la procédure “chargement driver / connexion” • Outil de préservation d’unicité : le singleton Connection cnx=Cnx.getCnx().getConnection(); • Dans un contexte Web, on peut passer par les séquences d’initialisations du serveur • Utilisation de Pools de connexion
  7. 7. 3 - Création de Statement // L’instance de Statement est générée par l’objet connexion Statement st=cnx.createStatement(); 
 // Exemple de requête avec résultat
 ResultSet rs=st.executeQuery("SELECT * FROM table");
 
 // Exemple de requête sans résultat
 st.executeUpdate
 ("DELETE FROM table WHERE num = 1");

  8. 8. 4 - Exploitation des résultats • Après le SELECT : Exploitation du ResultSet • Accès aux colonnes par : • Index (à partir de 1) XXX getXXX(int index); • Nom de colonne XXX getXXX(String nomCol);
  9. 9. Parcours d’un ResultSet • On utilise la méthode next(); • Certains drivers intègrent d’autres méthodes : • previous() • relative(int mv) • Toutes retournent un booléan pour inclusion directe dans un while(...)
  10. 10. Exemples de parcours d’un ResultSet while(rs.next())
 {
 int num=rs.getInt(“num”);
 String titre=rs.getString(“titre”);
 } while(rs.next())
 {
 int num=rs.getInt(1);
 String titre=rs.getString(2);
 }
  11. 11. 5 - Fin de connexion • Il faut penser à fermer tous les objets rs.close();
 st.close();
 cnx.close(); • Il est important de fermer la connexion • Nombre d’accès limités par SGBD • Ne pas monopoliser ces ressources ! • Cas particulier : si la connexion est dans un singleton
  12. 12. Récupération d’une clé générée • Certaines tables génèrent automatiquement leur clé lors d’un INSERT • (auto_increment sous MySQL) • Récupération de la clé après le INSERT • Avec JDBC>=3 : statement.getGeneratedKeys() • Sous MySQL : SELECT LAST_INSERT_ID • Autre SGBD : voir driver propriétaire
  13. 13. Récupération de la clé String req=”INSERT …“; st.executeUpdate(sql); // Récupération avec Statement.getGeneratedKeys() rs = st.getGeneratedKeys(); if (rs.next()) cle = rs.getInt(1); // Récupération avec MySQL LAST_INSERT_ID() rs = st.executeQuery("SELECT LAST_INSERT_ID()"); if (rs.next()) cle = rs.getInt(1);
  14. 14. Les statement préparés • Permet de faciliter l’écriture de requêtes complexes • Stockées dans le SGBD, seuls les paramètres sont envoyés d’un appel à l’autre • Permet d’éviter divers problèmes de syntaxe... • ...et divers problèmes de sécurité • + quelques bénéfices de performance
  15. 15. Exemple de PreparedStatement // Création du Statement PreparedStatement phrase= cnx.prepareStatement(  " INSERT into TABLE (chaine, entier, reel) VALUES (?,?,?) "); 
 // Mise en place des paramètres phrase.setString(1, "uneChaine"); phrase.setInt(2, 56); phrase.setDouble(3, 3.314456); 
 // Exécution de la requête phrase.executeUpdate(); Liste de paramètres
  16. 16. Intérêts du PreparedStatement • Légèrement plus efficace (pré-exécution sur le SGBD) • Evite des problèmes de syntaxe • Oublis de ‘ ‘ pour des chaînes,‘ intempestifs • Protège (partiellement) des injections SQL • Insertion illicites de requêtes
  17. 17. Traitements batchs Connection connection = ... ; Statement statement = connection.createStatement(); if(connection.getMetaData().supportsBatchUpdates()){ connection.setAutoCommit(false); statement.clearBatch(); //on supprime les anciens batch statement.addBatch("INSERT ...."); statement.addBatch("UPDATE ..."); statement.addBatch("..."); int[] resultat = statement.executeBatch(); //voir les différents types de retour possibles connection.commit(); connection.setAutoCommit(false); }
  18. 18. Intégration des accès à la base dans les objets • Un des rôles des objets métiers est de faire la liaison avec la base de données • 3 cas principaux sont à traiter : • Liaison simple une instance = un enreg • Instances encapsulant des listes • Structures hiérarchiques (héritage)
  19. 19. Exemple d’intégration d’appels à la base • Une classe User utilisée dans un site Web • On peut : • Se connecter avec un login/mot de passe • A vérifier avec un SELECT • S’enregistrer en donnant un login, un mot de passe, un nom, une adresse... • A concrétiser avec un INSERT INTO...
  20. 20. Structure de la classe User • C’est un objet CRUD 
 (Create/Read/Update/Delete) • Exemple de création/ enregistrement :
 User u=new User();
 u.setNom(“.....”);
 ...
 u.insert(); User login password nom select(login,password) insert() update() delete() On verra plus tard qu’il vaut mieux utiliser un pattern DAO
  21. 21. Procédure de login • Comment faire à la fois : • Contrôler si un login/pass est correct • Charger les données du user (nom...) • Empêcher qu’une instance de User soit incohérente en mémoire (login/pass incorrect mais encapsulés dans un objet) • Solution : on utilise un constructeur • Avec une lancée d’exception éventuelle
  22. 22. Constructeur de User public class User()
 {
 public User(String login, String pwd) 
 throws UserInexistantException()
 {
 String sql=”SELECT nom FROM user
 WHERE login=’”+login+”’ AND pwd=’”+pwd+”’”;
 if(rs.next()) // le login existe
 this.nom=rs.getString(“nom”); // on charge les données
 else // le login n’existe pas
 throw new LoginIncorrectException(login);
 }
  23. 23. Exploitation de liaisons 1-N • En objet : c’est un attribut qui contient des instances d’autres objets • En SGBD : c’est le résultat d’une requête avec une clé secondaire • Il va donc falloir faire une requête qui va remplir la liste • Problème : comment instancier chacun des éléments de la liste ?
  24. 24. Exemple de liste en objet public class Catalogue { private ArrayList<Produit> liste; public void rech(String texte) { // remplissage de la liste } … } public class Produit { private int cle; private String nom; ... public void load(int cle) { // lecture d’un enregistrement } }
  25. 25. Remplissage “procédural” public void rech(String texte) { sql=”SELECT * FROM produit WHERE ...”; while(...) { Produit p=new Produit(); p.setNom(rs.getString(“nom”)); ...
 liste.add(p); } } Problème : on gère la lecture d’un produit en dehors de la classe Produit -> non respect de la notion 
 d’encapsulation
  26. 26. Remplissage “objet” public void rech(String texte) { sql=”SELECT cle FROM produit WHERE ...”; while(...) { cle=rs.getInt(“cle”); Produit p=new Produit(); p.load(cle); liste.add(p); } } Problème : On génère un grand nombre de requête (N+1 requêtes, N étant le nombre de produits)
  27. 27. Solution mixte public void rech(String texte) { sql=”SELECT * FROM produit WHERE ...”; while(...) { Produit p=new Produit(); p.load(rs); liste.add(p); } } Dans cette solution, on passe à ‘load’ directement le ResultSet “rs” afin de traiter la requête à la source
  28. 28. L’héritage dans un modèle relationnel • Problématique : il n’est pas possible de définir directement une structure d’héritage dans un système de tables • Plusieurs solutions sont possibles : • 1 table regroupant tout • 1 table par classe fille • 1 table par classe fille + 1 table pour la classe mère
  29. 29. Modèle à une table • A partir d’une hiérarchie de classe Produit -> Cd ou Livre : Table produit : -refprod -type
 -nom -prix -dureecd -nbpageslivres La table contient à la fois les infos du CD, et celles du livre Cette solution est valable si CD et Livre ont peu de données divergentesLe type est encodé dans un champ
  30. 30. Une table par classe fille sans factorisation • A partir d’une hiérarchie de classe Produit -> Cd ou Livre : La table Livre contient toutes les données d’un livre Cette solution facilite les requêtes mais complique les recherches inter-catégories Table CD : -refprod -nom -prix -dureecd Table Livre : -refprod -nom -prix -nbpages
  31. 31. 1 table par classe fille + 1 table pour classe mère • A partir d’une hiérarchie de classe Produit -> Cd ou Livre : Table Produit : -refprod -type
 -nom -prix La table Livre contient uniquement les données propres au livre Cette solution concilie factorisation et particularité des types de produits Table CD : -refprod -dureecd Table Livre : -refprod -nbpages
  32. 32. Lecture base d’une classe polymorphe • On passe par une Factory qui va délivrer suivant les cas une instance de CD, de Livre... Produit p=ProduitFactory.getProduit(int cle); ‘p’ sera en fait une instance de CD, de Livre..
  33. 33. Factory modèle à 1 table public static Produit getProduit(int cle) { Produit p=null; sql=”SELECT * FROM produit WHERE ...”; if(...) { type=rs.getString(“type”); if(type.equals(“cd”)) { p=new CD(); p.load(rs); } } return p; }
  34. 34. Factory modèle à N tables public static Produit getProduit(int cle) { Produit p=null; p=new CD(); if(!p.load(cle)) { p=new Livre(); if(!p.load(cle)) { ... } else p=null; } return p; } On ne peut que faire des tests en cascade
  35. 35. Factory modèle à N tables +1 public static Produit getProduit(int cle) { Produit p=null; sql=”SELECT * FROM produit WHERE ...”; if(...) { type=rs.getString(“type”); if(type.equals(“cd”)) { p=new CD(); p.load(rs); } } return p; } C’est dans chacune des méthodes “load()” que l’on va faire une jointure entre les données ‘mère’ et ‘fille’
  36. 36. Conclusion sur ces différents scénarios • Le scénario « 1 table » est peu satisfaisant d’un point de vue SGBD, mais finalement limpide à gérer par la Factory • Le scénario 2 est un peu trop ‘batard’ pour être utile • Le scénario « N tables +1 » est un bon compromis difficulté/propreté de structure • D’autres structures sont envisageables (ex : tables décrivant des listes de champs)
  37. 37. Pattern DAO • Data Access Object • Permet de résoudre plusieurs problèmes : • Eviter que les instances d’objets métiers soient trop lourdes en mémoire • Permettre des traitements différenciés suivant les supports (lecture dans un fichier texte, dans un XML, dans une base…) • Séparer la partie métier d’aspects beaucoup plus techniques
  38. 38. Implémentation d’un DAO UserDAO<User,Integer> User select(login,password) insert(User) update(User) User login password nom UserMySQLDAO<User,Integer> Connection cnx User select(Integer i) User select(login,password) insert(User) update(User) DAO<T,U> <<abstract>> T select(U id) insert(T) update(T)
  39. 39. Utilisation d’un objet DAO User u=new User(); u.setNom(« Leponge »); u.setPrenom(« Bob »); UserDAO dao = new UserMySQLDAO(cnx); dao.insert(u);
  40. 40. Hibernate Persistence d’objet en Java
  41. 41. Rendre un objet persistant • Pour rendre un objet persistant, il suffit de le lier à la session • session.save(objet); • La clé pourra éventuellement être générée automatiquement, et délivrée par save() : • Long cle=(Long)session.save(objet); • (clé déclarée assigned dans le HBM) • Cette clé sera également dans objet.getRef()
  42. 42. Sauvegarde par saveOrUpdate • Si la clé a été renseignée dans l’objet : • Si la clé existe en base : UPDATE • Si la clé n’existe pas : INSERT • Si la clé n’a pas été renseignée : INSERT • Si l’objet existe en base et n’a pas été modifié en mémoire : pas de requête générée
  43. 43. Chargement d’un objet Client c=(Client)session.load(Client.class,new Long(12)); • ou : Client c=new Client();
 session.load(c,new Long(12)); • version sans lancement d’exception (renvoie null si l’objet n’existe pas) Client c=
 (Client)session.get(Client.class,new Long(12));
 if(c==null)
 c=new Client();
  44. 44. Requêtage HQL Query q=session.createQuery(“...”);
 q.setString(0,”..”);
 q.setInt(1,”...”);
 List l=q.list(); // émission de la requête • S’il n’y a qu’un résultat : Client c=(Client)q.uniqueResult();
  45. 45. Paramètres d’une requête• Par numéro : FROM Client WHERE nom=? AND age=? q.setString(0,”Dupont”); // commence à 0 • Par nom : FROM Client WHERE nom=:nom AND age=:age q.setString(“nom”,”Dupont”); • Listés : FROM Client WHERE nom IN (:liste) q.setParameterList(“liste”,unTableau);
  46. 46. Récupération plusieurs objets d’un coup Query q = sess.createQuery( "SELECT facture,client FROM Facture facture 
 JOIN Client facture.client client");
 List l=q.list();
 Iterator it=l.iterator(); while ( it.hasNext() ) { Object[] tuple = (Object[]) it.next(); Facture f= tuple[0]; Client c = tuple[1]; .... }
  47. 47. Association N-1 unidirectionnelle <class name="Item"> <id name="ref" column="refitem"> <generator class="native"/> </id>
 <many-to-one name=”prod” class=”Produit”> <column="refprod" not-null="true"/>
 </many-to-one> </class> <class name="Produit"> <id name="ref" column="refprod"> <generator class="native"/> </id> </class> create table Item ( 
 refitem bigint not null primary key
 refprod bigint not null ) create table Produit ( 
 refprod bigint not null primary key) public class Item ( 
 private Long ref;
 private Produit prod; ) public class Produit ( private Long ref;
 )
  48. 48. Association 1-1 unidirectionnelle <class name="Client"> <id name="ref" column="refcli"> <generator class="native"/> </id>
 <many-to-one name=”panier” class=”Panier”> <column="refpanier"
 unique=”true” not-null="true"/>
 </many-to-one> </class> <class name="Panier"> <id name="ref" column="refpanier"> <generator class="native"/> </id> </class> create table Client ( 
 refcli bigint not null primary key
 refpanier bigint not null ) create table Panier ( 
 refpanier bigint not null primary key) public class Client ( 
 private Long ref;
 private Panier panier; ) public class Panier ( private Long ref;
 )
  49. 49. Association 1-N unidirectionnelle <class name="Client"> <id name="ref" column="refclient"> <generator class="native"/> </id> <set name="factures"> <key column="refclient" not-null="true"/> <one-to-many class="Facture"/> </set> </class> <class name="Facture"> <id name="ref" column="reffacture"> <generator class="native"/> </id> </class> create table Client ( 
 refclient bigint not null primary key ) create table Facture ( 
 reffacture bigint not null primary key,
 refclient bigint not null ) public class Client ( 
 private Long ref;
 private List factures; ) public class Facture ( private Long ref;
 )
  50. 50. Représentation d’héritage avec une table <class name="Produit" table="produit" abstract=”true” discriminator-value=”-”> <id name="ref" column="ref"> <generator class="native"/> </id> <discriminator column="type" type="character"/> <property name="titre"/> 
 <subclass name="Livre" discriminator-value="L"> <property name="nbpages"/> </subclass> <subclass name="CD" discriminator-value="C"> <property name="duree"/> <property name="maisondisque"/> </subclass> </class> create table produit ( ref BIGINT not null, type CHAR(1) not null, titre VARCHAR(255), duree FLOAT, maisondisque VARCHAR(255), nbpages INTEGER, primary key (ref) )
  51. 51. Représentation d’héritage avec plusieurs tables <class name="Produit" table="produit" abstract=”true”> <id name="ref" column="refprod"> <generator class="native"/> </id> <discriminator column="type" type="character"/> <property name="titre"/> 
 <join-subclass name="CD" table="cd"> <key column="refprod"/> <property name="maisondisque"/> </subclass> </class> create table produit ( ref BIGINT not null, type CHAR(1) not null, titre VARCHAR(255), duree FLOAT, maisondisque VARCHAR(255), nbpages INTEGER, primary key (ref) ) create table cd ( ref BIGINT not null, maisondisque VARCHAR(255), primary key (ref) )

×