Ein Leitfaden für Jdbi

1. Einleitung

In diesem Artikel erfahren Sie, wie Sie eine relationale Datenbank mit jdbi abfragen.

Jdbi ist eine Open-Source-Java-Bibliothek (Apache-Lizenz), die Lambda-Ausdrücke und -Reflexionen verwendet, um eine benutzerfreundlichere Schnittstelle auf höherer Ebene als JDBC für den Zugriff auf die Datenbank bereitzustellen.

Jdbi ist jedoch kein ORM; Obwohl es über ein optionales SQL-Objektzuordnungsmodul verfügt, gibt es keine Sitzung mit angehängten Objekten, eine Datenbankunabhängigkeitsschicht und keine anderen Schnickschnack eines typischen ORM.

2. Jdbi-Setup

Jdbi ist in einen Kern und mehrere optionale Module unterteilt.

Um zu beginnen, 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 von jdbi3-core , HSQLDB und den anderen Jdbi-Modulen auf Maven Central finden.

3. Herstellen einer Verbindung zur Datenbank

Zuerst müssen wir eine Verbindung zur Datenbank herstellen. Dazu müssen wir die Verbindungsparameter angeben.

Der Ausgangspunkt ist die Jdbi- Klasse:

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 ein Properties- Objekt akzeptiert :

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 Jdbi- Instanz in einer lokalen Variablen gespeichert . Das liegt daran, dass wir damit Anweisungen und Abfragen an die Datenbank senden.

Tatsächlich stellt der bloße Aufruf von create keine Verbindung zur Datenbank her. Es werden nur die Verbindungsparameter für später gespeichert.

3.2. Verwenden einer DataSource

Wenn wir über eine DataSource eine Verbindung zur Datenbank herstellen , wie dies normalerweise der Fall ist, können wir die entsprechende Überladung zum Erstellen verwenden :

Jdbi jdbi = Jdbi.create(datasource);

3.3. Arbeiten mit Griffen

Tatsächliche Verbindungen zur Datenbank werden durch Instanzen der Handle- Klasse dargestellt.

Der einfachste Weg, mit Handles zu arbeiten und sie automatisch schließen zu lassen, ist die Verwendung von Lambda-Ausdrücken:

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

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

Ansonsten verwenden wir withHandle :

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

Wie wir sehen können, implementiert Handle Closeable zum Glück , sodass es mit Try-with-Resources verwendet werden kann.

4. Einfache Aussagen

Nachdem wir nun wissen, wie man eine Verbindung herstellt, wollen wir sehen, wie man sie verwendet.

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

Um Anweisungen wie create table an die Datenbank zu senden , verwenden wir die Methode execute :

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

Tatsächlich ist Execute nur eine bequeme Methode.

Wir werden uns in späteren Abschnitten mit komplexeren Anwendungsfällen befassen, aber bevor wir dies tun, müssen wir lernen, wie Ergebnisse aus der Datenbank extrahiert werden.

5. Abfragen der Datenbank

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

Um eine Abfrage mit einem Jdbi-Handle zu erstellen, müssen wir mindestens Folgendes tun:

  1. Erstellen Sie die Abfrage
  2. Wählen Sie aus, wie jede Zeile dargestellt werden soll
  3. iterieren Sie über die Ergebnisse

Wir werden uns nun jeden der obigen Punkte ansehen.

5.1. Abfrage erstellen

Es überrascht nicht, dass Jdbi Abfragen als Instanzen der Query- Klasse darstellt.

Wir können eine von einem Griff erhalten:

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

5.2. Zuordnung der Ergebnisse

Jdbi abstrahiert vom JDBC ResultSet , das eine ziemlich umständliche API hat.

Therefore, it offers several possibilities to access the columns resulting from a query or some other statement that returns a result. We'll now see the simplest ones.

We can represent each row as a map:

query.mapToMap();

The keys of the map will be the selected column names.

Or, when a query returns a single column, we can map it to the desired Java type:

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

Jdbi has built-in mappers for many common classes. Those that are specific to some library or database system are provided in separate modules.

Of course, we can also define and register our mappers. We'll talk about it in a later section.

Finally, we can map rows to a bean or some other custom class. Again, we'll see the more advanced options in a dedicated section.

5.3. Iterating Over the Results

Once we've decided how to map the results by calling the appropriate method, we receive a ResultIterable object.

We can then use it to iterate over the results, one row at a time.

Here we'll look at the most common options.

We can merely accumulate the results in a list:

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

Or to another Collection type:

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

Or we can iterate over the results as a stream:

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

Here, we explicitly typed the stream variable for clarity, but it's not necessary to do so.

5.4. Getting a Single Result

As a special case, when we expect or are interested in just one row, we have a couple of dedicated methods available.

If we want at most one result, we can use findFirst:

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

As we can see, it returns an Optional value, which is only present if the query returns at least one result.

If the query returns more than one row, only the first is returned.

If instead, we want one and only one result, we use findOnly:

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

Finally, if there are zero results or more than one, findOnly throws an IllegalStateException.

6. Binding Parameters

Often, queries have a fixed portion and a parameterized portion. This has several advantages, including:

  • security: by avoiding string concatenation, we prevent SQL injection
  • ease: we don't have to remember the exact syntax of complex data types such as timestamps
  • performance: the static portion of the query can be parsed once and cached

Jdbi supports both positional and named parameters.

We insert positional parameters as question marks in a query or statement:

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

Named parameters, instead, start with a colon:

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

In either case, to set the value of a parameter, we use one of the variants of the bind method:

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

Note that, unlike JDBC, indexes start at 0.

6.1. Binding Multiple Named Parameters at Once

We can also bind multiple named parameters together using an object.

Let's say we have this simple query:

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

Then, for example, we can use a map:

query.bindMap(params);

Or we can use an object in various ways. Here, for example, we bind an object that follows the JavaBean convention:

query.bindBean(paramsBean);

But we could also bind an object's fields or methods; for all the supported options, see the Jdbi documentation.

7. Issuing More Complex Statements

Now that we've seen queries, values, and parameters, we can go back to statements and apply the same knowledge.

Recall that the execute method we saw earlier is just a handy shortcut.

In fact, similarly to queries, DDL and DML statements are represented as instances of the class Update.

We can obtain one by calling the method createUpdate on a handle:

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

Then, on an Update we have all the binding methods that we have in a Query, so section 6. applies for updates as well.url

Statements are executed when we call, surprise, execute:

int rows = update.execute();

As we have already seen, it returns the number of affected rows.

7.1. Extracting Auto-Increment Column Values

As a special case, when we have an insert statement with auto-generated columns (typically auto-increment or sequences), we may want to obtain the generated values.

Then, we don't call execute, but executeAndReturnGeneratedKeys:

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 that we've seen previously, so we already know how to use it:

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

8. Transactions

We need a transaction whenever we have to execute multiple statements as a single, atomic operation.

As with connection handles, we introduce a transaction by calling a method with a closure:

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

And, as with handles, the transaction is automatically closed when the closure returns.

However, we must commit or rollback the transaction before returning:

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

If, however, an exception is thrown from the closure, Jdbi automatically rolls back the transaction.

As with handles, we have a dedicated method, inTransaction, if we want to return something from the closure:

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

8.1. Manual Transaction Management

Although in the general case it's not recommended, we can also begin and close a transaction manually:

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

9. Conclusions and Further Reading

In this tutorial, we've introduced the core of Jdbi: queries, statements, and transactions.

We've left out some advanced features, like custom row and column mapping and batch processing.

We also haven't discussed any of the optional modules, most notably the SQL Object extension.

Everything is presented in detail in the Jdbi documentation.

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