Java et bases de donnéesJDBC - Hibernate
Version du 05/12/2013
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)
Process de requétage1. 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
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é"); throw e; } }}
Accès direct au driver par une chaîne de
caractères
On utilise les prototypes
de java.sql
2 - Connexion à la base
try { dbC=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) ; }
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
3 - Création de Statement!
try{ // dbC est l'instance de 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"); rs.close(); } catch(SQLException e) {}
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);
Exemple de parcours d’un ResultSet
while(rs.next()) { int num=rs.getInt(“num”); String titre=rs.getString(“titre”); }
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(...)
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
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
Récupération de la cléString req=”INSERT …“; st.executeUpdate(sql); !// Récupération avec Statement.getGeneratedKeys()
rs = stmt.getGeneratedKeys(); if (rs.next()) cle = rs.getInt(1); !// Récupération avec MySQL LAST_INSERT_ID() rs = stmt.executeQuery("SELECT LAST_INSERT_ID()");
if (rs.next()) cle = rs.getInt(1);
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
Exemple de PreparedStatement
// Création du Statement
PreparedStatement phrase= dbC.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
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
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);!}
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)
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...
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) insert() update() delete()
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
Constructeurs de Userpublic 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); }
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 ?
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 } }
Remplissage “procédural”
public void rech(String texte) { sql=”SELECT * FROM produit WHERE ...”; while(...) { Produit p=new Produit(); p.nom=rs.getString(“nom”); ... liste.add(p); } }
Problème : on gère la lecture d’un produit en dehors de la classe Produit
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)
Solution mixtepublic void rech(String texte) { sql=”SELECT * FROM produit WHERE ...”; while(...) { cle=rs.getInt(“cle”); Produit p=new Produit(); p.load(rs); liste.add(p); } }
Dans cette solution, on passe à Produit directement l’objet “rs” afin de traiter la requête à la source
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 pour la classe mère
• 1 table par classe fille
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
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
Cette solution facilite les requêtes mais complique les recherches
inter-catégoriesTable CD : -refprod -type -nom -prix -dureecd
Table Livre : -refprod -type -nom -prix -nbpages
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
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);
Factory modèle à 1 tablepublic 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; }
Factory modèle à 2 tablespublic static Produit getProduit(int cle) { Produit p=null; p=new CD(); if(!p.load(rs)) { p=new Livre(); if(!p.load(rs)) { ... } else p=null; } return p; }
On ne peut que faire des tests en cascade
Factory modèle à 3 tablespublic 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 tables ‘mère’
et ‘fille’
HibernatePersistence d’objet en Java
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()
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
Chargement d’un objetClient 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();
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();
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);
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];! ....!}
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;)
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;)
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;)
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)!)
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)!)