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:
-
Erstellen Sie die Abfrage
-
Wählen Sie aus, wie die einzelnen Zeilen dargestellt werden sollen
-
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
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
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.