Eine Anleitung zu Apache Commons DbUtils

Eine Anleitung zu Apache Commons DbUtils

1. Überblick

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

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

2. Konfiguration

2.1. Maven-Abhängigkeiten

Zuerst müssen wir die Abhängigkeitencommons-dbutils undh2 zu unserenpom.xml hinzufügen:


    commons-dbutils
    commons-dbutils
    1.6


    com.h2database
    h2
    1.4.196

Sie finden die neueste Version voncommons-dbutils undh2 in Maven Central.

2.2. Datenbank testen

Erstellen wir mit unseren vorhandenen Abhängigkeiten ein Skript, 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]');
// ...

In allen Beispieltestfällen in diesem Artikel wird eine neu erstellte Verbindung zu einer H2-In-Memory-Datenbank verwendet:

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 benötigen 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 bietetthe QueryRunner class as the main entry point 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 eineResultSetHandler-Implementierung, die für die Umwandlung vonResultSet-Instanzen in die von unserer Anwendung erwarteten Objekte verantwortlich ist.

Natürlich bietet die Bibliothek bereits mehrere Implementierungen, die die gängigsten Transformationen wie Listen, Maps und JavaBeans verarbeiten.

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 mitMapListHandler:

@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 hier ein Beispiel mitBeanListHandler, um die Ergebnisse inEmployee 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 wirScalarHandler 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);
}

Um alle Implementierungen vonResultSerHandlerzu lernen, können Sie sich aufResultSetHandler documentationbeziehen.

4.1. Benutzerdefinierte Handler

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

Dies kann entweder durch Implementieren derResultSetHandler-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 unsererEmployee-Klasse ein weiteres Feld hinzu:

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

Erstellen wir nun eine Klasse, die den TypBeanListHandlererweitert 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 einConnection-Objekt 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 Tabelleemployeemit den Feldnamen unserer KlasseEmployeeü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 dieRowProcessor-Schnittstelle und ihre Implementierungen nutzen, um die Spaltennamen den entsprechenden Feldern in unseren Klassen zuzuordnen.

Mal sehen, wie das aussieht. Erstellen wir zunächst 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 die KlasseEmployeeHandler:

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 wirBeanProcessor verwenden, um die eigentliche Zuordnung von Spalten zu Feldern durchzuführen, und nur für diejenigen, die angesprochen werden müssen.

Lassen Sie uns zum Schluss testen, ob alles in Ordnung ist:

@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. Einfügen von Datensätzen

Die KlasseQueryRunnerbietet zwei Ansätze zum Erstellen von Datensätzen in einer Datenbank.

Die erste besteht darin, die Methodeupdate()zu verwenden und die SQL-Anweisung und eine optionale Liste von Ersetzungsparametern zu übergeben. Die Methode gibt die Anzahl der eingefügten Datensätze zurück:

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

Die zweite besteht darin, dieinsert()-Methode zu verwenden, die zusätzlich zur SQL-Anweisung und den Ersetzungsparametern einResultSetHandler benötigt, um die resultierenden automatisch generierten Schlüssel zu transformieren. Der Rückgabewert ist das, was der Handler zurückgibt:

@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. Aktualisieren und Löschen

Die Methodeupdate() der KlasseQueryRunner kann auch zum Ändern und Löschen von Datensätzen aus unserer Datenbank verwendet werden.

Seine Verwendung ist trivial. Hier ist ein Beispiel für die Aktualisierung des Gehalts eines Mitarbeiters:

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

Und hier ist eine andere Möglichkeit, einen Mitarbeiter mit der angegebenen ID zu löschen:

@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. Asynchrone Operationen

DbUtils stellt die KlasseAsyncQueryRunnerzur Verfügung, um Operationen asynchron auszuführen. Die Methoden für diese Klasse entsprechen denen der KlasseQueryRunner, mit der Ausnahme, dass sie eine Instanz vonFuturezurückgeben.

Hier ist ein Beispiel, um alle Mitarbeiter in der Datenbank abzurufen und bis zu 10 Sekunden auf die Ergebnisse zu warten:

@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. Fazit

In diesem Tutorial haben wir die wichtigsten Funktionen der Apache Commons DbUtils-Bibliothek untersucht.

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

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