Guide des DbUtils Apache Commons

1. Vue d'ensemble

Apache Commons DbUtils est une petite bibliothèque qui facilite grandement l'utilisation de JDBC.

Dans cet article, nous allons implémenter des exemples pour présenter ses fonctionnalités et capacités.

2. Configuration

2.1. Dépendances de Maven

Tout d'abord, nous devons ajouter les dépendances commons-dbutils et h2 à notre pom.xml :

 commons-dbutils commons-dbutils 1.6   com.h2database h2 1.4.196 

Vous pouvez trouver la dernière version de commons-dbutils et h2 sur Maven Central.

2.2. Base de données de test

Avec nos dépendances en place, créons un script pour créer les tables et les enregistrements que nous utiliserons:

CREATE TABLE employee( id int NOT NULL PRIMARY KEY auto_increment, firstname varchar(255), lastname varchar(255), salary double, hireddate date, ); CREATE TABLE email( id int NOT NULL PRIMARY KEY auto_increment, employeeid int, address varchar(255) ); INSERT INTO employee (firstname,lastname,salary,hireddate) VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy')); // ... INSERT INTO email (employeeid,address) VALUES (1, '[email protected]'); // ...

Tous les exemples de cas de test de cet article utiliseront une connexion nouvellement créée à une base de données en mémoire H2:

public class DbUtilsUnitTest { private Connection connection; @Before public void setupDB() throws Exception { Class.forName("org.h2.Driver"); String db = "jdbc:h2:mem:;INIT=runscript from 'classpath:/employees.sql'"; connection = DriverManager.getConnection(db); } @After public void closeBD() { DbUtils.closeQuietly(connection); } // ... }

2.3. POJO

Enfin, nous aurons besoin de deux classes simples:

public class Employee { private Integer id; private String firstName; private String lastName; private Double salary; private Date hiredDate; // standard constructors, getters, and setters } public class Email { private Integer id; private Integer employeeId; private String address; // standard constructors, getters, and setters }

3. Présentation

La bibliothèque DbUtils fournit la classe QueryRunner comme point d'entrée principal pour la plupart des fonctionnalités disponibles.

Cette classe fonctionne en recevant une connexion à la base de données, une instruction SQL à exécuter et une liste facultative de paramètres pour fournir des valeurs pour les espaces réservés de la requête.

Comme nous le verrons plus tard, quelques méthodes reçoivent également une implémentation de ResultSetHandler - qui est responsable de la transformation des instances de ResultSet en objets que notre application attend.

Bien sûr, la bibliothèque fournit déjà plusieurs implémentations qui gèrent les transformations les plus courantes, telles que les listes, les cartes et les JavaBeans.

4. Interrogation des données

Maintenant que nous connaissons les bases, nous sommes prêts à interroger notre base de données.

Commençons par un exemple rapide d'obtention de tous les enregistrements de la base de données sous forme de liste de cartes à l'aide d'un MapListHandler :

@Test public void givenResultHandler_whenExecutingQuery_thenExpectedList() throws SQLException { MapListHandler beanListHandler = new MapListHandler(); QueryRunner runner = new QueryRunner(); List list = runner.query(connection, "SELECT * FROM employee", beanListHandler); assertEquals(list.size(), 5); assertEquals(list.get(0).get("firstname"), "John"); assertEquals(list.get(4).get("firstname"), "Christian"); }

Ensuite, voici un exemple utilisant un BeanListHandler pour transformer les résultats en instances Employee :

@Test public void givenResultHandler_whenExecutingQuery_thenEmployeeList() throws SQLException { BeanListHandler beanListHandler = new BeanListHandler(Employee.class); QueryRunner runner = new QueryRunner(); List employeeList = runner.query(connection, "SELECT * FROM employee", beanListHandler); assertEquals(employeeList.size(), 5); assertEquals(employeeList.get(0).getFirstName(), "John"); assertEquals(employeeList.get(4).getFirstName(), "Christian"); }

Pour les requêtes qui retournent une valeur unique, nous pouvons utiliser un ScalarHandler :

@Test public void givenResultHandler_whenExecutingQuery_thenExpectedScalar() throws SQLException { ScalarHandler scalarHandler = new ScalarHandler(); QueryRunner runner = new QueryRunner(); String query = "SELECT COUNT(*) FROM employee"; long count = runner.query(connection, query, scalarHandler); assertEquals(count, 5); }

Pour connaître toutes les implémentations de ResultSerHandler , vous pouvez vous référer à la documentation ResultSetHandler .

4.1. Gestionnaires personnalisés

Nous pouvons également créer un gestionnaire personnalisé à transmettre aux méthodes de QueryRunner lorsque nous avons besoin de plus de contrôle sur la façon dont les résultats seront transformés en objets.

Cela peut être fait en implémentant l' interface ResultSetHandler ou en étendant l'une des implémentations existantes fournies par la bibliothèque.

Voyons à quoi ressemble la deuxième approche. Tout d'abord, ajoutons un autre champ à notre classe Employee :

public class Employee { private List emails; // ... }

Maintenant, créons une classe qui étend le type BeanListHandler et définit la liste de diffusion pour chaque employé:

public class EmployeeHandler extends BeanListHandler { private Connection connection; public EmployeeHandler(Connection con) { super(Employee.class); this.connection = con; } @Override public List handle(ResultSet rs) throws SQLException { List employees = super.handle(rs); QueryRunner runner = new QueryRunner(); BeanListHandler handler = new BeanListHandler(Email.class); String query = "SELECT * FROM email WHERE employeeid = ?"; for (Employee employee : employees) { List emails = runner.query(connection, query, handler, employee.getId()); employee.setEmails(emails); } return employees; } }

Notez que nous attendons un objet Connection dans le constructeur afin que nous puissions exécuter les requêtes pour obtenir les emails.

Enfin, testons notre code pour voir si tout fonctionne comme prévu:

@Test public void givenResultHandler_whenExecutingQuery_thenEmailsSetted() throws SQLException { EmployeeHandler employeeHandler = new EmployeeHandler(connection); QueryRunner runner = new QueryRunner(); List employees = runner.query(connection, "SELECT * FROM employee", employeeHandler); assertEquals(employees.get(0).getEmails().size(), 2); assertEquals(employees.get(2).getEmails().size(), 3); }

4.2. Processeurs de lignes personnalisés

Dans nos exemples, les noms de colonne de la table Employee correspondent aux noms de champ de notre classe Employee (la correspondance ne respecte pas la casse). Cependant, ce n'est pas toujours le cas, par exemple lorsque les noms de colonnes utilisent des traits de soulignement pour séparer les mots composés.

Dans ces situations, nous pouvons tirer parti de l' interface RowProcessor et de ses implémentations pour mapper les noms de colonnes aux champs appropriés de nos classes.

Voyons à quoi cela ressemble. Commençons par créer une autre table et y insérer des enregistrements:

CREATE TABLE employee_legacy ( id int NOT NULL PRIMARY KEY auto_increment, first_name varchar(255), last_name varchar(255), salary double, hired_date date, ); INSERT INTO employee_legacy (first_name,last_name,salary,hired_date) VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy')); // ...

Maintenant, modifions notre classe EmployeeHandler :

public class EmployeeHandler extends BeanListHandler { // ... public EmployeeHandler(Connection con) { super(Employee.class, new BasicRowProcessor(new BeanProcessor(getColumnsToFieldsMap()))); // ... } public static Map getColumnsToFieldsMap() { Map columnsToFieldsMap = new HashMap(); columnsToFieldsMap.put("FIRST_NAME", "firstName"); columnsToFieldsMap.put("LAST_NAME", "lastName"); columnsToFieldsMap.put("HIRED_DATE", "hiredDate"); return columnsToFieldsMap; } // ... }

Notez que nous utilisons un BeanProcessor pour effectuer le mappage réel des colonnes aux champs et uniquement pour ceux qui doivent être adressés.

Finally, let's test everything is ok:

@Test public void givenResultHandler_whenExecutingQuery_thenAllPropertiesSetted() throws SQLException { EmployeeHandler employeeHandler = new EmployeeHandler(connection); QueryRunner runner = new QueryRunner(); String query = "SELECT * FROM employee_legacy"; List employees = runner.query(connection, query, employeeHandler); assertEquals((int) employees.get(0).getId(), 1); assertEquals(employees.get(0).getFirstName(), "John"); }

5. Inserting Records

The QueryRunner class provides two approaches to creating records in a database.

The first one is to use the update() method and pass the SQL statement and an optional list of replacement parameters. The method returns the number of inserted records:

@Test public void whenInserting_thenInserted() throws SQLException { QueryRunner runner = new QueryRunner(); String insertSQL = "INSERT INTO employee (firstname,lastname,salary, hireddate) " + "VALUES (?, ?, ?, ?)"; int numRowsInserted = runner.update( connection, insertSQL, "Leia", "Kane", 60000.60, new Date()); assertEquals(numRowsInserted, 1); }

The second one is to use the insert() method that, in addition to the SQL statement and replacement parameters, needs a ResultSetHandler to transform the resulting auto-generated keys. The return value will be what the handler returns:

@Test public void givenHandler_whenInserting_thenExpectedId() throws SQLException { ScalarHandler scalarHandler = new ScalarHandler(); QueryRunner runner = new QueryRunner(); String insertSQL = "INSERT INTO employee (firstname,lastname,salary, hireddate) " + "VALUES (?, ?, ?, ?)"; int newId = runner.insert( connection, insertSQL, scalarHandler, "Jenny", "Medici", 60000.60, new Date()); assertEquals(newId, 6); }

6. Updating and Deleting

The update() method of the QueryRunner class can also be used to modify and erase records from our database.

Its usage is trivial. Here's an example of how to update an employee's salary:

@Test public void givenSalary_whenUpdating_thenUpdated() throws SQLException { double salary = 35000; QueryRunner runner = new QueryRunner(); String updateSQL = "UPDATE employee SET salary = salary * 1.1 WHERE salary <= ?"; int numRowsUpdated = runner.update(connection, updateSQL, salary); assertEquals(numRowsUpdated, 3); }

And here's another to delete an employee with the given id:

@Test public void whenDeletingRecord_thenDeleted() throws SQLException { QueryRunner runner = new QueryRunner(); String deleteSQL = "DELETE FROM employee WHERE id = ?"; int numRowsDeleted = runner.update(connection, deleteSQL, 3); assertEquals(numRowsDeleted, 1); }

7. Asynchronous Operations

DbUtils provides the AsyncQueryRunner class to execute operations asynchronously. The methods on this class have a correspondence with those of QueryRunner class, except that they return a Future instance.

Here's an example to obtain all employees in the database, waiting up to 10 seconds to get the results:

@Test public void givenAsyncRunner_whenExecutingQuery_thenExpectedList() throws Exception { AsyncQueryRunner runner = new AsyncQueryRunner(Executors.newCachedThreadPool()); EmployeeHandler employeeHandler = new EmployeeHandler(connection); String query = "SELECT * FROM employee"; Future
    
      future = runner.query(connection, query, employeeHandler); List employeeList = future.get(10, TimeUnit.SECONDS); assertEquals(employeeList.size(), 5); }
    

8. Conclusion

In this tutorial, we explored the most notable features of the Apache Commons DbUtils library.

Nous avons interrogé des données et les avons transformées en différents types d'objets, inséré des enregistrements obtenant les clés primaires générées et mis à jour et supprimés des données en fonction d'un critère donné. Nous avons également profité de la classe AsyncQueryRunner pour exécuter de manière asynchrone une opération de requête.

Et, comme toujours, le code source complet de cet article est disponible sur Github.