1. Java et bases de données
JDBC - Stratégies - Pattern DAO - Hibernate
Version du 25/08/2015
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Utilisation d’un objet
DAO
User u=new User();
u.setNom(« Leponge »);
u.setPrenom(« Bob »);
UserDAO dao = new UserMySQLDAO(cnx);
dao.insert(u);
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. 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. 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();
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. 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. 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. 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. 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. 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. 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)
)