Eine Anleitung zu Apache Commons DbUtils

1. Übersicht

Apache Commons DbUtils ist eine kleine Bibliothek, die die Arbeit mit JDBC erheblich erleichtert.

In diesem Artikel werden Beispiele implementiert, um die Funktionen und Fähigkeiten zu demonstrieren.

2. Setup

2.1. Maven-Abhängigkeiten

Zuerst müssen wir die Abhängigkeiten commons-dbutils und h2 zu unserer pom.xml hinzufügen :

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

Sie finden die neueste Version von commons-dbutils und h2 auf Maven Central.

2.2. Datenbank testen

Lassen Sie uns mit unseren vorhandenen Abhängigkeiten ein Skript erstellen, um die Tabellen und Datensätze zu erstellen, die wir verwenden werden:

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]'); // ...

Alle Beispieltestfälle in diesem Artikel verwenden eine neu erstellte Verbindung zu einer H2-In-Memory-Datenbank:

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. POJOs

Schließlich brauchen wir zwei einfache Klassen:

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. Einführung

Die DbUtils-Bibliothek bietet die QueryRunner- Klasse als Haupteinstiegspunkt für die meisten verfügbaren Funktionen.

Diese Klasse empfängt eine Verbindung zur Datenbank, eine auszuführende SQL-Anweisung und eine optionale Liste von Parametern, um Werte für die Platzhalter der Abfrage bereitzustellen.

Wie wir später sehen werden, erhalten einige Methoden auch eine ResultSetHandler- Implementierung, die für die Umwandlung von ResultSet- Instanzen in die von unserer Anwendung erwarteten Objekte verantwortlich ist.

Natürlich bietet die Bibliothek bereits mehrere Implementierungen, die die häufigsten Transformationen verarbeiten, z. B. Listen, Karten und JavaBeans.

4. Daten abfragen

Nachdem wir die Grundlagen kennen, können wir unsere Datenbank abfragen.

Beginnen wir mit einem kurzen Beispiel für das Abrufen aller Datensätze in der Datenbank als Liste von Karten mithilfe eines 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"); }

Als Nächstes sehen Sie ein Beispiel mit einem BeanListHandler, um die Ergebnisse in Employee- Instanzen umzuwandeln :

@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"); }

Für Abfragen, die einen einzelnen Wert zurückgeben, können wir einen ScalarHandler verwenden :

@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); }

Informationen zu allen ResultSerHandler- Implementierungen finden Sie in der ResultSetHandler- Dokumentation.

4.1. Benutzerdefinierte Handler

Wir können auch einen benutzerdefinierten Handler erstellen , der an die Methoden von QueryRunner übergeben wird , wenn wir mehr Kontrolle darüber benötigen, wie die Ergebnisse in Objekte umgewandelt werden.

Dies kann entweder durch Implementieren der ResultSetHandler- Schnittstelle oder durch Erweitern einer der vorhandenen Implementierungen erfolgen, die von der Bibliothek bereitgestellt werden.

Mal sehen, wie der zweite Ansatz aussieht. Fügen wir zunächst unserer Employee- Klasse ein weiteres Feld hinzu :

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

Jetzt erstellen wir eine Klasse, die den BeanListHandler- Typ erweitert und die E-Mail-Liste für jeden Mitarbeiter festlegt:

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; } }

Beachten Sie , dass wir im Konstruktor ein Verbindungsobjekt erwarten, damit wir die Abfragen ausführen können, um die E-Mails abzurufen.

Lassen Sie uns abschließend unseren Code testen, um festzustellen, ob alles wie erwartet funktioniert:

@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. Benutzerdefinierte Zeilenprozessoren

In unseren Beispielen stimmen die Spaltennamen der Mitarbeitertabelle mit den Feldnamen unserer Mitarbeiterklasse überein (bei der Übereinstimmung wird die Groß- und Kleinschreibung nicht berücksichtigt). Dies ist jedoch nicht immer der Fall - beispielsweise wenn Spaltennamen Unterstriche verwenden, um zusammengesetzte Wörter zu trennen.

In diesen Situationen können wir die RowProcessor- Schnittstelle und ihre Implementierungen nutzen, um die Spaltennamen den entsprechenden Feldern in unseren Klassen zuzuordnen.

Mal sehen, wie das aussieht. Zuerst erstellen wir eine weitere Tabelle und fügen einige Datensätze ein:

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')); // ...

Ändern wir nun unsere EmployeeHandler- Klasse:

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; } // ... }

Beachten Sie, dass wir einen BeanProcessor verwenden , um die eigentliche Zuordnung von Spalten zu Feldern durchzuführen , und nur für diejenigen, die angesprochen werden müssen.

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.

Wir haben Daten abgefragt und in verschiedene Objekttypen umgewandelt, Datensätze eingefügt, die die generierten Primärschlüssel erhalten, und Daten basierend auf bestimmten Kriterien aktualisiert und gelöscht. Wir haben auch die AsyncQueryRunner- Klasse genutzt, um eine Abfrageoperation asynchron auszuführen.

Und wie immer finden Sie den vollständigen Quellcode für diesen Artikel auf Github.