Introduction à JDBC

1. Vue d’ensemble

Dans cet article, nous allons examiner JDBC (Java Database Connectivity), une API permettant de connecter et d’exécuter des requêtes sur une base de données.

JDBC peut fonctionner avec n’importe quelle base de données tant que les pilotes appropriés sont fournis.

2. Pilotes JDBC

Un pilote JDBC est une implémentation d’API JDBC utilisée pour se connecter à un type de base de données particulier. Il existe plusieurs types de pilotes JDBC:

  • Type 1 - contient un mappage vers une autre API d’accès aux données; un exemple de

c’est le pilote JDBC-ODBC ** Type 2 - est une implémentation qui utilise les bibliothèques côté client du

base de données cible; également appelé pilote d’API natif ** Type 3 - utilise un middleware pour convertir les appels JDBC en bases de données spécifiques

appels; aussi appelé pilote de protocole réseau ** Type 4: connexion directe à une base de données en convertissant les appels JDBC en

appels spécifiques à la base de données; appelés pilotes de protocole de base de données ou pilotes légers,

Le type le plus couramment utilisé est le type 4, car il présente l’avantage d’être indépendant de la plate-forme . La connexion directe à un serveur de base de données offre de meilleures performances que d’autres types. L’inconvénient de ce type de pilote est qu’il est spécifique à la base de données - chaque base de données ayant son propre protocole spécifique.

3. Connexion à une base de données

Pour se connecter à une base de données, il suffit d’initialiser le pilote et d’ouvrir une connexion à la base de données.

3.1. Enregistrement du pilote

Pour notre exemple, nous allons utiliser un pilote de protocole de base de données de type 4.

Comme nous utilisons une base de données MySQL, nous avons besoin de https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22mysql-connector-java%22%20AND%20g%3A%22mysql % 22[ mysql-connector-java ]dépendance:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

Ensuite, enregistrons le pilote en utilisant la méthode Class.forName () , qui charge dynamiquement la classe de pilote:

Class.forName("com.mysql.cj.jdbc.Driver");

3.2. Création de la connexion

Pour ouvrir une connexion, nous pouvons utiliser la méthode getConnection () de la classe DriverManager . Cette méthode nécessite un paramètre d’URL de connexion String :

Connection con = DriverManager
  .getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass");

La syntaxe de l’URL de connexion dépend du type de base de données utilisé.

Jetons un coup d’œil à quelques exemples:

jdbc:mysql://localhost:3306/myDb?user=user1&password=pass
jdbc:postgresql://localhost/myDb
jdbc:hsqldb:mem:myDb

Pour vous connecter à la base de données spécifiée myDb , vous devez créer la base de données et un utilisateur, puis ajouter un accès nécessaire:

CREATE DATABASE myDb;
CREATE USER 'user1' IDENTIFIED BY 'pass';
GRANT ALL on myDb.**  TO 'user1';

4. Exécuter des instructions SQL

Pour envoyer des instructions SQL à la base de données, nous pouvons utiliser des instances de type Statement , PreparedStatement ou CallableStatement . Celles-ci sont obtenues à l’aide de l’objet Connection .

4.1. Déclaration

L’interface Statement contient les fonctions essentielles à l’exécution de commandes SQL.

Commençons par créer un objet Statement :

Statement stmt = con.createStatement();

L’exécution d’instructions SQL peut se faire de trois manières:

  • executeQuery () pour les instructions SELECT

  • executeUpdate () pour mettre à jour les données ou la structure de la base de données

  • execute () peut être utilisé dans les deux cas précédents lorsque le résultat est

inconnu

Utilisons la méthode execute () pour ajouter une table students à notre base de données:

String tableSql = "CREATE TABLE IF NOT EXISTS employees"
  + "(emp__id int PRIMARY KEY AUTO__INCREMENT, name varchar(30),"
  + "position varchar(30), salary double)";
stmt.execute(tableSql);
  • Si la méthode execute () est utilisée pour mettre à jour les données, la méthode stmt.getUpdateCount () renvoie le nombre de lignes concernées. **

Si le résultat est 0, aucune ligne n’a été affectée ou il s’agissait d’une commande de mise à jour de la structure de la base de données.

Si la valeur est -1, alors la commande était une requête SELECT. Le résultat peut ensuite être obtenu à l’aide de stmt.getResultSet () .

Ensuite, ajoutons un enregistrement à notre table en utilisant la méthode executeUpdate () :

String insertSql = "INSERT INTO employees(name, position, salary)"
  + " VALUES('john', 'developer', 2000)";
stmt.executeUpdate(insertSql);

La méthode retourne le nombre de lignes affectées pour une commande qui met à jour des lignes ou 0 pour une commande qui met à jour la structure de la base de données.

Nous pouvons récupérer les enregistrements de la table en utilisant la méthode executeQuery () qui retourne un objet de type ResultSet :

String selectSql = "SELECT **  FROM employees";
ResultSet resultSet = stmt.executeQuery(selectSql);

4.2. Affirmation préparée

Les objets PreparedStatement contiennent des séquences SQL précompilées. Ils peuvent avoir un ou plusieurs paramètres désignés par un point d’interrogation.

Créons un PreparedStatement qui met à jour les enregistrements de la table employees en fonction de paramètres donnés:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp__id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);

Pour ajouter des paramètres au PreparedStatement , nous pouvons utiliser des paramètres simples - setX () - où X est le type du paramètre et les arguments de la méthode sont l’ordre et la valeur du paramètre:

pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

L’instruction est exécutée avec l’une des trois méthodes décrites précédemment: executeQuery (), executeUpdate (), execute () sans le paramètre SQL String :

int rowsAffected = pstmt.executeUpdate();

4.3. CallableStatement

L’interface CallableStatement permet d’appeler des procédures stockées.

Pour créer un objet CallableStatement , nous pouvons utiliser la méthode prepareCall () de Connection :

String preparedSql = "{call insertEmployee(?,?,?,?)}";
CallableStatement cstmt = con.prepareCall(preparedSql);

La définition des valeurs de paramètre d’entrée pour la procédure stockée s’effectue comme dans l’interface PreparedStatement , à l’aide des méthodes setX () :

cstmt.setString(2, "ana");
cstmt.setString(3, "tester");
cstmt.setDouble(4, 2000);

Si la procédure stockée a des paramètres de sortie, nous devons les ajouter à l’aide de la méthode registerOutParameter () :

cstmt.registerOutParameter(1, Types.INTEGER);

Exécutons ensuite l’instruction et récupérons la valeur renvoyée à l’aide de la méthode getX () correspondante:

cstmt.execute();
int new__id = cstmt.getInt(1);

Par exemple, pour fonctionner, nous devons créer la procédure stockée dans notre base de données MySql:

delimiter//CREATE PROCEDURE insertEmployee(OUT emp__id int,
  IN emp__name varchar(30), IN position varchar(30), IN salary double)
BEGIN
INSERT INTO employees(name, position,salary) VALUES (emp__name,position,salary);
SET emp__id = LAST__INSERT__ID();
END//delimiter ;

La procédure insertEmployee ci-dessus insère un nouvel enregistrement dans la table employees à l’aide des paramètres donnés et renvoie l’ID du nouvel enregistrement dans le paramètre emp id__ out.

Pour pouvoir exécuter une procédure stockée à partir de Java, l’utilisateur de la connexion doit avoir accès aux métadonnées de la procédure stockée. Ceci peut être réalisé en accordant des droits à l’utilisateur sur toutes les procédures stockées dans toutes les bases de données:

GRANT ALL ON mysql.proc TO 'user1';

Alternativement, nous pouvons ouvrir la connexion avec la propriété noAccessToProcedureBodies définie sur true :

con = DriverManager.getConnection(
  "jdbc:mysql://localhost:3306/myDb?noAccessToProcedureBodies=true",
  "user1", "pass");

Cela informera l’API JDBC que l’utilisateur n’a pas le droit de lire les métadonnées de la procédure, de sorte qu’il créera tous les paramètres en tant que paramètres INOUT String .

5. Analyse des résultats de la requête

Après l’exécution d’une requête, le résultat est représenté par un objet ResultSet , doté d’une structure similaire à une table, avec des lignes et des colonnes.

5.1. ResultSet Interface

ResultSet utilise la méthode next () pour passer à la ligne suivante.

Commençons par créer une classe Employee pour stocker nos enregistrements récupérés:

public class Employee {
    private int id;
    private String name;
    private String position;
    private double salary;

   //standard constructor, getters, setters
}

Traversons ensuite le ResultSet et créons un objet Employee pour chaque enregistrement:

String selectSql = "SELECT **  FROM employees";
ResultSet resultSet = stmt.executeQuery(selectSql);

List<Employee> employees = new ArrayList<>();

while (resultSet.next()) {
    Employee emp = new Employee();
    emp.setId(resultSet.getInt("emp__id"));
    emp.setName(resultSet.getString("name"));
    emp.setPosition(resultSet.getString("position"));
    emp.setSalary(resultSet.getDouble("salary"));
    employees.add(emp);
}

La récupération de la valeur de chaque cellule du tableau peut être effectuée à l’aide de méthodes de type getX ( ), où X représente le type des données de la cellule.

Les méthodes getX () peuvent être utilisées avec un paramètre int représentant l’ordre de la cellule ou un paramètre String représentant le nom de la colonne. Cette dernière option est préférable au cas où nous changerions l’ordre des colonnes dans la requête.

5.2. ResultSet pouvant être mis à jour

De manière implicite, un objet ResultSet peut uniquement être déplacé vers l’avant et ne peut pas être modifié.

Si nous voulons utiliser ResultSet pour mettre à jour les données et les parcourir dans les deux sens, nous devons créer l’objet Statement avec des paramètres supplémentaires:

stmt = con.createStatement(
  ResultSet.TYPE__SCROLL__INSENSITIVE,
  ResultSet.CONCUR__UPDATABLE
);

Pour naviguer sur ce type de ResultSet , nous pouvons utiliser l’une des méthodes suivantes:

  • first (), last (), beforeFirst (), beforeLast () - pour passer à la première

ou dernière ligne d’un ResultSet ou à la ligne qui précède ** next (), previous () - pour naviguer en avant et en arrière dans la

ResultSet ** getRow () – pour obtenir le numéro de ligne actuel

  • moveToInsertRow (), moveToCurrentRow () - pour passer à une nouvelle ligne vide

pour insérer et revenir à l’actuel si sur une nouvelle ligne ** absolute (int row) – pour passer à la ligne spécifiée

  • relative (int nrRows) - pour déplacer le curseur du nombre de lignes donné

La mise à jour de ResultSet peut être effectuée à l’aide de méthodes au format updateX () où X est le type des données de la cellule. Ces méthodes ne mettent à jour que l’objet ResultSet et non les tables de la base de données.

Pour conserver les modifications de ResultSet dans la base de données, vous devez utiliser l’une des méthodes suivantes:

  • updateRow () - pour conserver les modifications apportées à la ligne actuelle

base de données ** insertRow (), deleteRow () - pour ajouter une nouvelle ligne ou supprimer la ligne en cours.

un de la base de données ** refreshRow () - pour actualiser le ResultSet avec les modifications apportées au

base de données ** cancelRowUpdates () - pour annuler les modifications apportées à la ligne en cours

Prenons un exemple d’utilisation de certaines de ces méthodes en mettant à jour les données de la table employee’s :

Statement updatableStmt = con.createStatement(
  ResultSet.TYPE__SCROLL__INSENSITIVE, ResultSet.CONCUR__UPDATABLE);
ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql);

updatableResultSet.moveToInsertRow();
updatableResultSet.updateString("name", "mark");
updatableResultSet.updateString("position", "analyst");
updatableResultSet.updateDouble("salary", 2000);
updatableResultSet.insertRow();

6. Métadonnées d’analyse

L’API JDBC permet de rechercher des informations sur la base de données, appelées métadonnées.

6.1. DatabaseMetadata

L’interface DatabaseMetadata peut être utilisée pour obtenir des informations générales sur la base de données telles que les tables, les procédures stockées ou le dialecte SQL.

Voyons rapidement comment récupérer des informations sur les tables de la base de données:

DatabaseMetaData dbmd = con.getMetaData();
ResultSet tablesResultSet = dbmd.getTables(null, null, "%", null);
while (tablesResultSet.next()) {
    LOG.info(tablesResultSet.getString("TABLE__NAME"));
}

6.2. ResultSetMetadata

Cette interface peut être utilisée pour rechercher des informations sur un certain ResultSet , telles que le nombre et le nom de ses colonnes:

ResultSetMetaData rsmd = rs.getMetaData();
int nrColumns = rsmd.getColumnCount();

IntStream.range(1, nrColumns).forEach(i -> {
    try {
        LOG.info(rsmd.getColumnName(i));
    } catch (SQLException e) {
        e.printStackTrace();
    }
});

7. Traitement des transactions

Par défaut, chaque instruction SQL est validée juste après son achèvement.

Cependant, il est également possible de contrôler les transactions par programme .

Cela peut être nécessaire dans les cas où nous souhaitons préserver la cohérence des données, par exemple lorsque nous souhaitons uniquement valider une transaction si une précédente s’est terminée avec succès.

Tout d’abord, nous devons définir la propriété autoCommit de Connection sur false , puis utiliser les méthodes commit () et rollback () pour contrôler la transaction.

Ajoutons une deuxième instruction de mise à jour pour la colonne salary après la mise à jour de la colonne employee position et enveloppons-les dans une transaction.

De cette façon, le salaire ne sera mis à jour que si le poste a été mis à jour avec succès:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp__id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);
pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

String updateSalarySql = "UPDATE employees SET salary=? WHERE emp__id=?";
PreparedStatement pstmt2 = con.prepareStatement(updateSalarySql);
pstmt.setDouble(1, 3000);
pstmt.setInt(2, 1);

boolean autoCommit = con.getAutoCommit();
try {
    con.setAutoCommit(false);
    pstmt.executeUpdate();
    pstmt2.executeUpdate();
    con.commit();
} catch (SQLException exc) {
    con.rollback();
} finally {
    con.setAutoCommit(autoCommit);
}

8. Fermer la connexion

Lorsque nous n’utilisons plus, il est nécessaire de fermer la connexion pour libérer les ressources de la base de données.

Cela peut être fait en utilisant l’API close () :

con.close();

9. Conclusion

Dans ce didacticiel, nous avons examiné les bases du travail avec l’API JDBC.

Comme toujours, le code source complet des exemples est disponible à l’adresse over sur GitHub .