Ein Leitfaden für Jdbi

Ein Leitfaden für Jdbi

1. Einführung

In diesem Artikel wird erläutert, wie eine relationale Datenbank mitjdbi abgefragt wird.

Jdbi ist eine Open-Source-Java-Bibliothek (Apache-Lizenz), dielambda expressions undreflection verwendet, um eine freundlichere Schnittstelle auf höherer Ebene alsJDBC für den Zugriff auf die Datenbank bereitzustellen.

Jdbi, however, isn’t an ORM; Obwohl es ein optionales SQL-Objektzuordnungsmodul gibt, gibt es keine Sitzung mit angehängten Objekten, einer Datenbankunabhängigkeitsschicht und anderen Schnickschnack eines typischen ORM.

2. Jdbi Setup

Jdbi ist in einen Kern und mehrere optionale Module unterteilt.

Um anzufangen, müssen wir nur das Kernmodul in unsere Abhängigkeiten aufnehmen:


    
        org.jdbi
        jdbi3-core
        3.1.0
    

Im Verlauf dieses Artikels werden Beispiele anhand der HSQL-Datenbank gezeigt:


    org.hsqldb
    hsqldb
    2.4.0
    test

Wir können die neueste Version vonjdbi3-core,HSQLDB und den anderen Jdbi-Modulen auf Maven Central finden.

3. Verbindung zur Datenbank herstellen

Zuerst müssen wir uns mit der Datenbank verbinden. Dazu müssen wir die Verbindungsparameter angeben.

Der Ausgangspunkt ist die KlasseJdbi:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");

Hier geben wir die Verbindungs-URL, einen Benutzernamen und natürlich ein Passwort an.

3.1. Zusätzliche Parameter

Wenn wir andere Parameter angeben müssen, verwenden wir eine überladene Methode, die das ObjektPropertiesakzeptiert:

Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);

In diesen Beispielen haben wir die Instanz vonJdbiin einer lokalen Variablen gespeichert. Dies liegt daran, dass wir damit Anweisungen und Abfragen an die Datenbank senden.

Wenn Sie nurcreate aufrufen, wird keine Verbindung zur Datenbank hergestellt. Es speichert nur die Verbindungsparameter für später.

3.2. Verwenden vonDataSource

Wenn wir wie gewöhnlich eineDataSource mit der Datenbank verbinden, können wir die entsprechendecreate-Überladung verwenden:

Jdbi jdbi = Jdbi.create(datasource);

3.3. Arbeiten mit Griffen

Tatsächliche Verbindungen zur Datenbank werden durch Instanzen der KlasseHandledargestellt.

Die einfachste Möglichkeit, mit Griffen zu arbeiten und sie automatisch schließen zu lassen, ist die Verwendung von Lambda-Ausdrücken:

jdbi.useHandle(handle -> {
    doStuffWith(handle);
});

Wir rufenuseHandle auf, wenn wir keinen Wert zurückgeben müssen.

Andernfalls verwenden wirwithHandle:

jdbi.withHandle(handle -> {
    return computeValue(handle);
});

Es ist auch möglich, obwohl nicht empfohlen, ein Verbindungshandle manuell zu öffnen. In diesem Fall müssen wir es schließen, wenn wir fertig sind:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
    doStuffWith(handle);
}

Glücklicherweise implementiertHandle, wie wir sehen können,Closeable, sodass es mittry-with-resources verwendet werden kann.

4. Einfache Aussagen

Nachdem wir nun wissen, wie Sie eine Verbindung herstellen können, wollen wir sehen, wie Sie sie verwenden.

In diesem Abschnitt erstellen wir eine einfache Tabelle, die wir im gesamten Artikel verwenden.

Um Anweisungen wiecreate table an die Datenbank zu senden, verwenden wir die Methodeexecute:

handle.execute(
  "create table project "
  + "(id integer identity, name varchar(50), url varchar(100))");

execute gibt die Anzahl der Zeilen zurück, die von der Anweisung betroffen waren:

int updateCount = handle.execute(
  "insert into project values "
  + "(1, 'tutorials', 'github.com/eugenp/tutorials')");

assertEquals(1, updateCount);

Execute ist eigentlich nur eine einfache Methode.

Wir werden uns in späteren Abschnitten mit komplexeren Anwendungsfällen befassen. Zuvor müssen wir jedoch lernen, wie Sie Ergebnisse aus der Datenbank extrahieren.

5. Abfragen der Datenbank

Der einfachste Ausdruck, der Ergebnisse aus der Datenbank erzeugt, ist eine SQL-Abfrage.

Um eine Abfrage mit einem Jdbi-Handle auszugeben, müssen wir mindestens:

  1. Erstellen Sie die Abfrage

  2. Wählen Sie aus, wie die einzelnen Zeilen dargestellt werden sollen

  3. Durchlaufen Sie die Ergebnisse

Wir werden uns nun jeden der oben genannten Punkte ansehen.

5.1. Abfrage erstellen

Es überrascht nicht, dassJdbi represents queries as instances of the Query class.

Wir können eine aus einem Griff erhalten:

Query query = handle.createQuery("select * from project");

5.2. Zuordnung der Ergebnisse

Jdbi abstrahiert von den JDBCResultSet, die eine ziemlich umständliche API haben.

Daher bietet es mehrere Möglichkeiten, auf die Spalten zuzugreifen, die sich aus einer Abfrage oder einer anderen Anweisung ergeben, die ein Ergebnis zurückgibt. Wir werden jetzt die einfachsten sehen.

Wir können jede Zeile als Karte darstellen:

query.mapToMap();

Die Schlüssel der Karte sind die ausgewählten Spaltennamen.

Wenn eine Abfrage eine einzelne Spalte zurückgibt, können wir sie dem gewünschten Java-Typ zuordnen:

handle.createQuery("select name from project").mapTo(String.class);

Jdbi has built-in mappers for many common classes. Diejenigen, die für eine Bibliothek oder ein Datenbanksystem spezifisch sind, werden in separaten Modulen bereitgestellt.

Natürlich können wir auch unsere Mapper definieren und registrieren. Wir werden in einem späteren Abschnitt darüber sprechen.

Schließlich können wir einer Bean oder einer anderen benutzerdefinierten Klasse Zeilen zuordnen. Die erweiterten Optionen finden Sie in einem speziellen Abschnitt.

5.3. Iterieren über die Ergebnisse

Sobald wir entschieden haben, wie die Ergebnisse durch Aufrufen der entsprechenden Methodewe receive a ResultIterable object. zugeordnet werden sollen

Wir können es dann verwenden, um die Ergebnisse zeilenweise zu durchlaufen.

Hier sehen wir uns die gängigsten Optionen an.

Wir können die Ergebnisse lediglich in einer Liste zusammenfassen:

List> results = query.mapToMap().list();

Oder zu einem anderenCollection-Typ:

List results = query.mapTo(String.class).collect(Collectors.toSet());

Oder wir können die Ergebnisse als Stream durchlaufen:

query.mapTo(String.class).useStream((Stream stream) -> {
    doStuffWith(stream)
});

Hier haben wir aus Gründen der Übersichtlichkeit explizit die Variablestreameingegeben, dies ist jedoch nicht erforderlich.

5.4. Ein einziges Ergebnis erhalten

Als Sonderfall, wenn wir nur eine Zeile erwarten oder daran interessiert sind, stehen uns einige spezielle Methoden zur Verfügung.

Wenn wirat most one result wollen, können wirfindFirst verwenden:

Optional> first = query.mapToMap().findFirst();

Wie wir sehen können, gibt es einenOptional-Wert zurück, der nur vorhanden ist, wenn die Abfrage mindestens ein Ergebnis zurückgibt.

Wenn die Abfrage mehr als eine Zeile zurückgibt, wird nur die erste zurückgegeben.

Wenn wir stattdessenone and only one result wollen, verwenden wirfindOnly:

Date onlyResult = query.mapTo(Date.class).findOnly();

Wenn es schließlich null oder mehr als eins gibt, wirftfindOnlyIllegalStateException.

6. Bindungsparameter

Oftqueries have a fixed portion and a parameterized portion. Dies hat mehrere Vorteile, einschließlich:

  • Sicherheit: Indem wir die Verkettung von Zeichenfolgen vermeiden, verhindern wir die SQL-Injection

  • Leichtigkeit: Wir müssen uns nicht an die genaue Syntax komplexer Datentypen wie Zeitstempel erinnern

  • Leistung: Der statische Teil der Abfrage kann einmal analysiert und zwischengespeichert werden

Jdbi unterstützt sowohl Positions- als auch Named-Parameter.

Wir fügen Positionsparameter als Fragezeichen in eine Abfrage oder Anweisung ein:

Query positionalParamsQuery =
  handle.createQuery("select * from project where name = ?");

Benannte Parameter beginnen stattdessen mit einem Doppelpunkt:

Query namedParamsQuery =
  handle.createQuery("select * from project where url like :pattern");

In beiden Fällen verwenden wir zum Festlegen des Werts eines Parameters eine der Varianten der Methodebind:

positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");

Beachten Sie, dass Indizes im Gegensatz zu JDBC bei 0 beginnen.

6.1. Mehrere benannte Parameter gleichzeitig binden

Wir können auch mehrere benannte Parameter unter Verwendung eines Objekts zusammenbinden.

Nehmen wir an, wir haben diese einfache Abfrage:

Query query = handle.createQuery(
  "select id from project where name = :name and url = :url");
Map params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");

Dann können wir zum Beispiel eine Karte verwenden:

query.bindMap(params);

Oder wir können ein Objekt auf verschiedene Arten verwenden. Hier binden wir zum Beispiel ein Objekt, das der JavaBean-Konvention folgt:

query.bindBean(paramsBean);

Wir könnten aber auch die Felder oder Methoden eines Objekts binden. Alle unterstützten Optionen finden Sie unterthe Jdbi documentation.

7. Komplexere Anweisungen ausgeben

Nachdem wir Abfragen, Werte und Parameter gesehen haben, können wir zu Anweisungen zurückkehren und dasselbe Wissen anwenden.

Denken Sie daran, dass die Methodeexecute, die wir zuvor gesehen haben, nur eine praktische Abkürzung ist.

Ähnlich wie bei Abfragen istDDL and DML statements are represented as instances of the class Update.

Wir können eine erhalten, indem wir die MethodecreateUpdate für ein Handle aufrufen:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");

Dann haben wir auf einemUpdate alle Bindungsmethoden, die wir auf einemQuery haben, also Abschnitt 6. gilt auch für updates.url

Anweisungen werden ausgeführt, wenn wir überraschen,execute aufrufen:

int rows = update.execute();

Wie wir bereits gesehen haben, wird die Anzahl der betroffenen Zeilen zurückgegeben.

7.1. Extrahieren von Spaltenwerten mit automatischer Inkrementierung

Als Sonderfall möchten wir möglicherweise die generierten Werte erhalten, wenn wir eine Einfügeanweisung mit automatisch generierten Spalten haben (normalerweise automatisch inkrementiert oder mit Sequenzen).

Dann nennen wir nichtexecute, sondernexecuteAndReturnGeneratedKeys:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) "
  + "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();

ResultBearing is the same interface implemented by the Query class, die wir zuvor gesehen haben, sodass wir bereits wissen, wie man es verwendet:

generatedKeys.mapToMap()
  .findOnly().get("id");

8. Transaktionen

Wir benötigen eine Transaktion, wenn wir mehrere Anweisungen als einzelne atomare Operation ausführen müssen.

Wie bei Verbindungshandles führen wir eine Transaktion ein, indem wir eine Methode mit einem Closure aufrufen:

handle.useTransaction((Handle h) -> {
    haveFunWith(h);
});

Und wie bei Handles wird die Transaktion automatisch geschlossen, wenn der Abschluss zurückkehrt.

However, we must commit or rollback the transaction vor der Rückgabe:

handle.useTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
});

Wenn jedoch eine Ausnahme vom Abschluss ausgelöst wird, setzt Jdbi die Transaktion automatisch zurück.

Wie bei Handles haben wir eine dedizierte Methode,inTransaction, wenn wir etwas aus dem Abschluss zurückgeben möchten:

handle.inTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
    return true;
});

8.1. Manuelles Transaktionsmanagement

Obwohl dies im Allgemeinen nicht empfohlen wird, können wir eine Transaktion auch manuellbegin undclose durchführen:

handle.begin();
// ...
handle.commit();
handle.close();

9. Schlussfolgerungen und weiterführende Literatur

In diesem Tutorial haben wir den Kern von Jdbi vorgestellt:queries, statements, and transactions.

Wir haben einige erweiterte Funktionen wie die benutzerdefinierte Zeilen- und Spaltenzuordnung und die Stapelverarbeitung ausgelassen.

Wir haben auch keines der optionalen Module besprochen, insbesondere die SQL Object-Erweiterung.

Alles wird detailliert inJdbi documentation dargestellt.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inthe GitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein.