Skip to content

6. [Cours]: Inleiding tot API JDBC

Trefwoorden: relationele databases, API JDBC, SQLException.

6.1. Support

De map [support / chap-06] bevat de Eclipse-projecten van dit hoofdstuk.

6.2. Architecture

De laag JDBC (Java DataBase Connectivity) is een universele interface voor toegang tot databases. Deze biedt altijd dezelfde interface aan de laag [DAO]. Als u van SGBD overschakelt, hoeft u alleen de driver JDBC te wijzigen. De laag [DAO] blijft ongewijzigd.

6.3. De stappen bij het gebruik van een database

In de bovenstaande architectuur omvat de exploitatie van een database door het consoleprogramma de volgende stappen:

  1. het laden van de driver JDBC van de database;
  2. het openen van een verbinding met de database;
  3. het uitvoeren van een opdracht SQL op de database en het verwerken van de resultaten van de opdracht SQL;
  4. de verbinding afsluiten;

Stap 1 hoeft slechts één keer te worden uitgevoerd. De stappen 2-4 worden herhaaldelijk uitgevoerd. Let op: er wordt geen verbinding open gelaten. Deze wordt gesloten zodra deze niet meer nodig is.

6.3.1. stap 1 - het laden van de driver JDBC in het geheugen

De code


        // stuurprogramma laden JDBC
        try {
            Class.forName(nom de la classe du pilote JDBC);
        } catch (ClassNotFoundException e1) {
             // de uitzondering afhandelen
}

De bewerking op regel 3 heeft tot doel de driver JDBC uit de database in het geheugen te laden. Deze bewerking hoeft slechts één keer te worden uitgevoerd. Herhaling leidt echter niet tot een fout. Er wordt gezocht naar de klasse van de driver JDBC in het classpath van het project. Het is dus noodzakelijk dat in het Eclipse-project het bestand [jar], dat de klasse van de driver JDBC bevat, is opgenomen in het classpath van het project.

6.3.2. Stap 2 – Een verbinding openen

Zodra de driver JDBC is geïnstalleerd, wordt deze gevraagd een verbinding te openen met de BD:

De code


package spring.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class IntroJdbc01 {

...
        Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
...
        } catch (SQLException e1) {
            // de uitzondering wordt verwerkt
            ...
        } finally {
         // verbinding sluiten
         if (connexion != null) {
            try {
                connexion.close();
            } catch (SQLException e2) {
                // de uitzondering afhandelen
                ...
            }
         }
}
  • regels 3-7: de implementatieklassen van de interface JDBC bevinden zich allemaal in het pakket [java.sql]. Bovendien genereren ze bij een fout allemaal een uitzondering van het type [SQLException] (regel 19, 27). Deze uitzondering is afgeleid van de klasse [Exception] en is een zogenaamde gecontroleerde uitzondering: men is verplicht een try/catch-blok te gebruiken om deze af te vangen, of als alternatief deze niet af te vangen en aan te geven dat de methode de uitzondering doorlaat door de signatuur van de methode aan te vullen met [throws SQLException];
  • regel 17, [DriverManager.getConnection] is een statische methode die drie parameters verwacht:
    • [url]: de URL uit de database. Dit is een tekenreeks die afhankelijk is van de gebruikte BD. Voor MySQL heeft deze de vorm [jdbc:mysql://localhost:3306/nom_de_la_bd];
    • [user]: de eigenaar van de verbinding;
    • [passwd]: zijn wachtwoord;
  • regels 24-30: de verbinding moet in de clausule [finally] worden gesloten, zodat deze wordt gesloten, ongeacht of er een uitzondering optreedt of niet.

6.3.3. stap 3 - het verzenden van opdrachten SQL en [SELECT]

Zodra er een verbinding tot stand is gebracht, kunnen SQL-opdrachten worden verzonden. De manier waarop leesopdrachten [SELECT] worden afgehandeld, verschilt van die voor bijwerkingsoperaties [UPDATE, INSERT, DELETE]. We beginnen met de opdrachten SQL en [SELECT]:

De code


Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie starten
            connexion.setAutoCommit(false);
            // in alleen-lezenmodus
            connexion.setReadOnly(true);
            // de tabel [PRODUITS] wordt gelezen
            ps = connexion.prepareStatement("SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS");
            rs = ps.executeQuery();
            System.out.println("Liste des produits : ");
            while (rs.next()) {
                System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
            }
            // transactie vastleggen
            connexion.commit();
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
             doCatchException(connexion,e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(rs, ps, connexion);
        }

    private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
  • regels 8, 10: het openen van een transactie (regel 8) in de alleen-lezenmodus (regel 10). Een transactie is een reeks opdrachten SQL die ofwel allemaal slagen, ofwel allemaal mislukken. Dus in een transactie met N opdrachten SQL: als opdracht I+1 mislukt, worden de voorgaande I opdrachten geannuleerd. Voor een leesbewerking is een transactie niet nodig. Het aanmaken van een alleen-lezen-transactie kan echter bepaalde SGBD in staat stellen bepaalde optimalisaties door te voeren;
  • regel 12: gebruik van een [PreparedStatement]. Een [PreparedStatement] heeft normaal gesproken parameters die worden aangeduid met het teken ?. Hier zijn die er niet. Een [PreparedStatement] is een opdracht die wordt voorbereid door de SGBD. Deze voorbereiding brengt kosten met zich mee en wordt slechts één keer uitgevoerd. Vervolgens wordt deze voorbereide opdracht uitgevoerd door de SGBD met verschillende effectieve parameters die de formele parameters ? zullen vervangen. Merk op dat het beter is om de gewenste kolommen te benoemen in plaats van de notatie * te gebruiken om alle kolommen op te halen. Door de namen van de kolommen te specificeren, kunnen we vervolgens hun waarden ophalen op basis van hun positie in de query SELECT;
  • regel 13: uitvoering van [PreparedStatement]. We halen een object van het type [ResultSet] op;

Een object van het type [ResultSet] vertegenwoordigt een tabel, dat wil zeggen een verzameling rijen en kolommen. Op een bepaald moment heeft men slechts toegang tot één rij van de tabel, de zogenaamde huidige rij. Bij de eerste aanmaak van [ResultSet] is er geen huidige rij. Er moet een bewerking [ResultSet.next()] worden uitgevoerd om deze te verkrijgen. De signatuur van de methode next is als volgt:

    boolean next()

Deze methode probeert door te gaan naar de volgende regel van de [ResultSet] en retourneert true als dit lukt, anders false. Als de bewerking slaagt, wordt de volgende regel de nieuwe huidige regel. De vorige regel gaat verloren en kan niet meer worden teruggehaald.

De tabel van [ResultSet] heeft kolommen met de namen labelCol1, labelCol2, ... zoals gespecificeerd in de uitgevoerde query [SELECT]. Met de query:

SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
  • komt de kolom [ID] terecht in een kolom van [ResultSet] met de naam [myId];
  • de kolom [NOM] wordt ondergebracht in een kolom van de tabel [ResultSet] met de naam [myNom];
  • ...

Hierboven worden de identificatiecodes [myCol] kolomlabels genoemd. Zonder deze labels zijn de kolomnamen van de tabel [ResultSet] afhankelijk van de tabel SGBD. Wanneer het [SELECT] op één enkele tabel wordt uitgevoerd, zijn de kolomlabels standaard de kolomnamen die door het SELECT worden opgevraagd. Het probleem doet zich voor wanneer de [SELECT] op meerdere tabellen wordt uitgevoerd en er in deze tabellen identieke kolomnamen voorkomen, zoals in het volgende voorbeeld:

SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID

waarbij we aannemen dat de tabel [PRODUITS] een vreemde sleutel heeft naar de tabel [CATEGORIES], weergegeven door de relatie [Produits].CATEGORIE_ID --> [CATEGORIES].ID, en dat de tabellen [PRODUITS] en [CATEGORIES] beide een veld [NOM] hebben. In dit geval zijn de namen die in de tabel [ResultSet] aan de kolommen [PRODUITS.NOM] en [CATEGORIES.NOM] zijn gegeven, afhankelijk van de tabel SGBD. Voor de overdraagbaarheid tussen SGBD moeten hier dus kolomlabels worden gebruikt en schrijven we:


SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID

Om de verschillende velden van de huidige regel van [ResultSet] te benutten, zijn de volgende methoden beschikbaar:

Type getType("labelColi") 

om de kolom met de naam „labelColi” van de huidige regel op te halen en dus de kolom van het [SELECT]-record met dit label. Type verwijst naar het type van het veld coli. De volgende [getType]-methoden kunnen worden gebruikt: getInt, getLong, getString, getDouble, getFloat, getDate, ... In plaats van de kolomnaam te gebruiken, kan men de positie ervan in de uitgevoerde [SELECT]-query gebruiken:

Type getType(i) 

waarbij i de index is van de gewenste kolom (i>=1).

  • regels 15-17: ophalen van de waarden die zijn gelezen in de BD;
  • regel 19: de transactie wordt gevalideerd (ook wel ‘committed’ genoemd). Hierdoor wordt de transactie beëindigd en worden de bronnen vrijgegeven die de transactie SGBD ervoor had gereserveerd;
  • regel 25: de resources worden vrijgegeven in de [finally]. Deze roept de volgende methode [doFinally] aan:

private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
        // afsluiting ResultSet
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e1) {

            }
        }
        // afsluiten [PreparedStatement]
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e2) {

            }
        }
        if (connexion != null) {
            try {
                // verbinding verbreken
                connexion.close();
            } catch (SQLException e3) {
                 // de uitzondering afhandelen
            }
        }
    }
  • regels 3-9: afsluiting van [ResultSet];
  • regels 11-17: afsluiting van [PreparedStatement];
  • regels 18-27: verbinding verbreken;

Het afsluiten in de regels 3-17 lijkt overbodig, aangezien de verbinding in de regels 18-25 wordt afgesloten. In sommige gevallen is dit echter niet het geval en wordt aangeraden om ze te laten staan ([http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection]).

  • regel 22: de uitzondering wordt afgehandeld door de volgende methode [doCatchException]:

    private static void doCatchException(Connection connexion, Throwable th) {
        // transactie annuleren
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // de uitzondering afhandelen
        }
}
  • regels 4-6: de transactie wordt geannuleerd. Hiermee wordt de transactie beëindigd en kan de methode SGBD de daarvoor ingezette bronnen vrijgeven;

6.3.4. stap 3 – verzending van opdrachten SQL [INSERT, UPDATE, DELETE]

De opdrachten SQL en [INSERT, UPDATE, DELETE] zijn bijwerkingsbewerkingen: ze wijzigen de database, maar leveren geen rijen op. De enige informatie die wordt geretourneerd, is het aantal rijen waarop de bijwerkingsbewerking betrekking heeft.

De code


Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie starten
            connexion.setAutoCommit(false);
            // in lees-/schrijfmodus
            connexion.setReadOnly(false);
            // de tabel wordt bijgewerkt
            ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
            // categorie 1
            ps.setInt(1, 10);
            // uitvoering
            int nbLignes=ps.executeUpdate();
            // transactie vastleggen
            connexion.commit();
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException(connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, ps, connexion);
        }
    }
  • regel 9: de verbinding wordt gebruikt voor zowel lezen als schrijven;
  • regel 11: een [PreparedStatement] met 1 parameter (aangeduid met ?). Er kunnen meerdere parameters zijn. Deze worden genummerd vanaf 1;
  • regel 13: de waarde wordt toegewezen aan de enige parameter. De eerste parameter van [setType] is de positie van de parameter in de [PreparedStatement] (1, 2, ...) en de tweede is de waarde die eraan wordt toegewezen. Men kan de methoden [setInt, setLong, setFloat, setDouble, setString, setDate, ...] gebruiken;
  • regel 15: hier wordt de methode [executeUpdate] gebruikt en niet [executeQuery], die is gereserveerd voor opdrachten van het type SELECT. De methode retourneert het aantal regels dat door de bewerking is beïnvloed. Dit kan 0 zijn.
  • regel 17: de transactie wordt gevalideerd;

6.3.5. stap 4 – het afsluiten van de verbinding

Een verbinding moet in een multi-user-omgeving zo snel mogelijk worden gesloten, omdat een SGBD slechts een beperkt aantal open verbindingen toestaat. In de voorgaande voorbeelden werd de verbinding gesloten in de clausule [finally] van de bewerkingen SQL, zodat deze altijd wordt gesloten, ongeacht of er een uitzondering is opgetreden of niet.

6.4. Een voorbeeldproject

6.4.1. Ondersteuning

De map [support / chap5] bevat de Eclipse-projecten van dit hoofdstuk [1, 2]. De map [database] bevat het script SQL waarmee de voorbeelddatabase MySQL van dit hoofdstuk [1, 3] kan worden aangemaakt.

6.4.2. De gebruikte database

De volgende voorbeelden maken gebruik van de volgende database MySQL:

 
  • [ID]: primaire sleutel in de modus AUTO_INCREMENT (als er geen primaire sleutel wordt opgegeven, genereert SGBD deze);
  • [NOM]: naam van een product – uniek;
  • [CATEGORIE]: het categorienummer;
  • [PRIX]: de prijs;
  • [DESCRIPTION]: een beschrijving van het product;

We maken deze aan met de tool [WampServer] op de volgende manier: [1-9]:

6.4.3. Het Eclipse-project

  

Het project is een Maven-project dat wordt gedefinieerd door het volgende bestand: [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.jdbc</groupId>
    <artifactId>intro-jdbc-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.1</version>
        </dependency>
    </dependencies>
</project>
  • regels 8-12: de driver JDBC van SGBD MySQL5;
  • regels 13-17: een bibliotheek die jSON (JavaScript Object Notation) kan verwerken (zie paragraaf 22.6). We gaan deze gebruiken om de producten uit de database in jSON-formaat weer te geven;

6.4.4. De productklasse

De klasse [Produit] ziet er als volgt uit:


package istia.st.jdbc;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Produit {

    // velden
    private int id;
    private String nom;
    private int categorie;
    private double prix;
    private String description;

    // constructors
    public Produit() {

    }

    public Produit(int id, String nom, int categorie, double prix, String description) {
        this.id = id;
        this.nom = nom;
        this.categorie = categorie;
        this.prix = prix;
        this.description = description;
    }

    // getters en setters
    ...

    // naar String
    public String toString() {
        try {
            return new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • regel 34: we gebruiken de bibliotheek jSON om de tekenreeks jSON van het product weer te geven. Dit levert een weergave op die er ongeveer zo uitziet:
Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}

Het voordeel van de bovenstaande methode [toString] is dat, als er velden aan de klasse worden toegevoegd of verwijderd, de methode [toString] altijd geldig blijft. Bovendien, als de velden zelf objecten zijn (lijsten, arrays, woordenboeken, gebruikersobjecten), kunnen de bibliotheken jSON deze op hun beurt omzetten in een tekenreeks jSON;

6.4.5. De klasse [Static]

De klasse [Static] bundelt in methoden de code die vaak in de hoofdklasse wordt gebruikt:


package istia.st.jdbc;

import java.util.ArrayList;
import java.util.List;

public class Static {

    public static List<String> getErreursFromThrowable(Throwable th) {
        // de lijst met foutmeldingen van de uitzondering ophalen
        List<String> erreurs = new ArrayList<String>();
        while (th != null) {
            // foutmelding van de throwable
            erreurs.add(th.getMessage());
            // we gaan naar de oorzaak van de throwable
            th = th.getCause();
        }
        // resultaat
        return erreurs;
    }
    
    public static void show(String title, List<String> messages){
        // titel
        System.out.println(String.format("%s : ",title));
        // berichten
        for(String message : messages){
            System.out.println(String.format("- %s",message));
        }
    }
}
  • regels 8-19: hiermee kan de lijst met fouten worden opgehaald, ingekapseld in een object van het type [Throwable], de bovenliggende klasse van de klasse [Exception];
  • regels 21-28: geeft een lijst met berichten weer op het scherm;

Deze code zou in de hoofdklasse kunnen staan, omdat dit de enige klasse is die er gebruik van maakt. We hebben hier echter te maken met een breder scenario waarin ook andere klassen deze code nodig zouden hebben.

6.4.6. Het raamwerk van de hoofdklasse


package istia.st.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class IntroJdbc01 {

    // constanten
    final static String url = "jdbc:mysql://localhost:3306/dbIntroJdbc";
    final static String user = "root";
    final static String passwd = "";
    final static String insert = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
    final static String delete = "DELETE FROM PRODUITS";
    final static String select = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
    final static String update = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
    final static String insert2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";

    public static void main(String[] args) {
        // het laden van de driver JDBC van MySQL
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e1) {
            doCatchException("Pilote JDBC introuvable", null, e1);
            return;
        }
        // de tabel [PRODUITS] wordt geleegd
        delete();
        // de tabel wordt gevuld
        insert();
        // de tabel wordt gelezen
        select();
        // bijgewerkt
        update();
        // weergave
        select();
        // twee identieke elementen invoegen
        // het invoegen moet mislukken en geen van beide elementen wordt ingevoegd vanwege de transactie
        insert2();
        // er wordt gecontroleerd
        select();
        // klaar
        System.out.println("Travail terminé");
    }

    // productlijst
    private static void select() {
...
    }

    // producten verwijderen
    public static void delete() {
...
    }

    // producten toevoegen
    public static void insert() {
...
    }

    // 2 producten toevoegen
    public static void insert2() {
...
    }

    // bepaalde producten bijwerken
    public static void update() {
..
    }

    private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
        // sluiting ResultSet
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e1) {

            }
        }
        // afsluiten [PreparedStatement]
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e2) {

            }
        }
        // verbinding verbreken
        if (connexion != null) {
            try {
                connexion.close();
            } catch (SQLException e3) {
                // foutmeldingen worden weergegeven
                Static.show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
                        Static.getErreursFromThrowable(e3));
            }
        }
    }

    private static void doCatchException(String title, Connection connexion, Throwable th) {
        // foutmeldingen worden weergegeven
        Static.show(title, Static.getErreursFromThrowable(th));
        // transactie annuleren
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // foutmeldingen weergeven
            Static.show(title, Static.getErreursFromThrowable(e2));
        }
    }
}

6.4.7. De inhoud van de producttabel verwijderen

De methode [delete] verwijdert de inhoud van de tabel:


// producten verwijderen
    public static void delete() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie starten
            connexion.setAutoCommit(false);
            // de tabel [PRODUITS] wordt geleegd
            ps = connexion.prepareStatement(delete);
            ps.executeUpdate();
            // transactie vastleggen
            connexion.commit();
            // terug naar standaardmodus
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
    }

In dit voorbeeld wordt gebruikgemaakt van transacties. Met een transactie kunnen opdrachten SQL worden gebundeld, die allemaal moeten slagen of allemaal moeten worden geannuleerd. Er zijn vier bewerkingen die u moet kennen:

  • een transactie starten: [connexion.setAutoCommit(false)];
  • succesvolle afronding van een transactie: [connexion.commit()]. In dit geval worden alle bewerkingen die tijdens de transactie op BD zijn uitgevoerd, gevalideerd;
  • einde van een mislukte transactie: [connexion.rollback()]. In dit geval worden alle bewerkingen die tijdens de transactie op BD zijn uitgevoerd, ongedaan gemaakt;
  • terugkeer naar de modus [auto-commit], de standaardmodus van de API JDBC: [connexion.setAutoCommit(true)]. In deze modus wordt elke opdracht SQL als een transactie behandeld. Als er dus twee invoegingen worden uitgevoerd waarvan de tweede mislukt:
    • in de modus [AutoCommit=true] blijft de eerste invoeging behouden (deze is gevalideerd door de eerste AutoCommit);
    • in de modus [AutoCommit=false] wordt de eerste invoeging ongedaan gemaakt;

In onze voorbeelden breken we de transactie telkens wanneer er een uitzondering optreedt af in de methode [doCatchException]:


    private static void doCatchException(String title, Connection connexion, Throwable th) {
        // foutmeldingen weergeven
        Static.show(title, Static.getErreursFromThrowable(th));
        // transactie annuleren
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // foutmeldingen weergeven
            Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
        }
}

6.4.8. Aanmaken van de inhoud van de producttabel

De methode [insert] maakt de inhoud van de tabel aan:


// producten toevoegen
    public static void insert() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie starten
            connexion.setAutoCommit(false);
            // de tabel wordt gevuld
            ps = connexion.prepareStatement(insert);
            for (int i = 0; i < 10; i++) {
                // voorbereiding
                int n = i + 1;
                ps.setInt(1, n);
                ps.setString(2, String.format("NOM%s", n));
                ps.setInt(3, n / 5 + 1);
                ps.setDouble(4, 100 * (1 + (double) i / 100));
                ps.setString(5, String.format("DESC%s", n));
                // uitvoering
                ps.executeUpdate();
            }
            // transactie vastleggen
            connexion.commit();
            // terug naar de standaardmodus
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
    }

6.4.9. Weergave van de inhoud van de producttabel

De methode [select] geeft de inhoud van de tabel weer:


    // productlijst
    private static void select() {
        Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie starten
            connexion.setAutoCommit(false);
            // de tabel wordt gelezen [PRODUITS]
            ps = connexion.prepareStatement(select);
            rs = ps.executeQuery();
            System.out.println("Liste des produits : ");
            while (rs.next()) {
                System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
            }
            // transactie vastleggen
            connexion.commit();
            // terug naar de standaardmodus
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
}

6.4.10. De inhoud van de tabel bijwerken

De methode [update] werkt bepaalde producten bij:


// bepaalde producten bijwerken
    public static void update() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // begin transactie
            connexion.setAutoCommit(false);
            // de tabel wordt bijgewerkt
            ps = connexion.prepareStatement(update);
            // categorie 1
            ps.setInt(1, 1);
            // uitvoering
            ps.executeUpdate();
            // transactie vastleggen
            connexion.commit();
            // terug naar standaardmodus
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
    }

6.4.11. Rol van de transactie

De methode [insert2] voegt twee producten met dezelfde primaire sleutel toe aan de tabel, wat niet mogelijk is. Aangezien we ons in een transactie bevinden, wordt de eerste invoeging ongedaan gemaakt.


// toevoegin g van 2 producten met dezelfde primaire sleutels
    public static void insert2() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // verbinding openen
            connexion = DriverManager.getConnection(url, user, passwd);
            // transactie gestart
            connexion.setAutoCommit(false);
            // er wordt 1 rij toegevoegd
            ps = connexion.prepareStatement(insert2);
            // uitvoering
            ps.executeUpdate();
            // dezelfde regel wordt een tweede keer toegevoegd, dus met dezelfde primaire sleutel
            // het invoegen moet mislukken en geen van beide elementen mag worden ingevoegd vanwege de transactie
            ps.executeUpdate();
            // transactie vastleggen
            connexion.commit();
            // terug naar de standaardmodus
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
    }

6.4.12. Resultaten

De uitvoering van de methode [main] levert de volgende resultaten op:

Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}
{"id":3,"nom":"NOM3","categorie":1,"prix":102.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":103.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":110.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":111.0,"description":"DESC2"}
{"id":3,"nom":"NOM3","categorie":1,"prix":112.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":113.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Les erreurs suivantes se sont produites lors de l'ajout : 
- Duplicate entry '100' for key 'PRIMARY'
Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":110.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":111.0,"description":"DESC2"}
{"id":3,"nom":"NOM3","categorie":1,"prix":112.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":113.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Travail terminé

6.5. Gebruik van een gegevensbron van het type [DataSource]

We gaan verder met de vorige toepassing en gebruiken daarbij een gegevensbron van het type [javax.sql.DataSource]:

Image

We gaan een gegevensbron gebruiken die is geïmplementeerd door de klasse [org.apache.tomcat.jdbc.pool.DataSource]. Deze klasse maakt gebruik van een verbindingspool, d.w.z. een verzameling geopende verbindingen:

  • wanneer de pool wordt geïnstantieerd, wordt een bepaald aantal verbindingen met de database geopend. Dit aantal is configureerbaar;
  • wanneer de Java-code een verbinding opent, wordt deze geleverd door de pool;
  • wanneer de Java-code een verbinding sluit, wordt deze teruggegeven aan de pool;

Uiteindelijk worden de verbindingen slechts één keer geopend, wat de prestaties bij het benaderen van de database verbetert. De gegevensbron wordt gedefinieerd in een Spring-configuratieklasse

6.5.1. Het Eclipse-project

  

Het project is een Maven-project dat wordt gedefinieerd door het volgende bestand [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.jdbc</groupId>
    <artifactId>intro-jdbc-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
        <!-- bibliotheek jSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.5.1</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>8.0.20</version>
        </dependency>
    </dependencies>
</project>
  • regels 21-25: afhankelijkheid van Spring;
  • regels 27-31: afhankelijkheid van de bibliotheek die de gegevensbron levert;

De Spring-configuratieklasse [AppConfig] ziet er als volgt uit:


package istia.st.jdbc;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    // constanten
    final static String URL = "jdbc:mysql://localhost:3306/dbIntroJdbc";
    final static String USER = "root";
    final static String PASSWD = "";
    final static String INSERT = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
    final static String DELETE = "DELETE FROM PRODUITS";
    final static String SELECT = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
    final static String UPDATE = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
    final static String INSERT2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
    final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
    
    @Bean
    public DataSource dataSource() {
        // gegevensbron TomcatJdbc
        DataSource dataSource = new DataSource();
        // toegangscontfiguratie JDBC
        dataSource.setDriverClassName(DRIVER_CLASSNAME);
        dataSource.setUsername(USER);
        dataSource.setPassword(PASSWD);
        dataSource.setUrl(URL);
        // een aanvankelijk geopende verbinding
        dataSource.setInitialSize(1);
        // resultaat
        return dataSource;
    }
}
  • regels 11-19: de constanten die eerder in [IntroJdbc01] waren gedefinieerd, zijn verplaatst naar [AppConfig];
  • regels 31-34: de Spring-bean die de gegevensbron definieert;
  • regel 24: aanmaken van de nog niet geconfigureerde gegevensbron;
  • regels 26-29: de gegevens waarmee de gegevensbron verbinding kan maken met de database;
  • regel 31: hier wordt een pool van 1 verbinding aangemaakt. Meer is hier niet nodig. Er zijn nooit meerdere gelijktijdige verbindingen;

6.5.2. De hoofdklasse

De hoofdklasse [IntroJdbc02] ziet er als volgt uit:


package istia.st.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IntroJdbc02 {
    // gegevensbron
    private static DataSource dataSource;

    public static void main(String[] args) {
        // ophalen van de Spring-context
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        // het ophalen van de gegevensbron
        dataSource = ctx.getBean(DataSource.class);
        // de tabel [PRODUITS] wordt leeggemaakt
        delete();
...
        // klaar
        ctx.close();
        System.out.println("Travail terminé");
    }

    // productlijst
    private static void select() {
        Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // verbinding openen
            connexion = dataSource.getConnection();
            // transactie starten
            connexion.setAutoCommit(false);
...
        } catch (SQLException e1) {
            // de uitzondering wordt afgehandeld
            doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
        } finally {
            // de finally-blok wordt verwerkt
            doFinally(null, null, connexion);
        }
    }
...
}
  • regel 14: de gegevensbron. Merk op dat deze van het type [javax.sql.DataSource] is, wat een interface is;
  • regel 18: instantiëren van de Spring-objecten;
  • regel 20: het verkrijgen van een verwijzing naar de gegevensbron. Merk op dat de daadwerkelijk gebruikte klasse op geen enkel moment wordt genoemd. Hier wijst dus niets erop dat er een implementatie van het type [TomcatJdbc] wordt gebruikt;
  • regel 36: het verkrijgen van een open verbinding;
  • de rest van de code is identiek aan die van de klasse [IntroJdbc01];

6.6. Conclusion

Meer informatie over databasebeheer is te vinden in het document [Exploiter une base relationnelle avec l'écosystème Spring].