JAVA, JDBC et liaison base de données

Preview:

DESCRIPTION

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

Citation preview

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)!)

Recommended