Einstieg in Spring Boot, Teil 13 Datenbankabfragen mit dem JDBC-Template

Von Dr. Dirk Koller |

Einfache Datenbanken-Abfragen via JPA, Repositories und Derived- und Custom-Queries sind in Spring oft die bevorzugte Methode. Es gibt aber Ausnahmen, zum Beispiel die Wartung von bestehenden Projekten, in denen bereits umfangreicher SQL-Code vorliegt.

Anbieter zum Thema

Mit dem JDBCTemplate bietet Spring eine Klasse, die SQL-Anweisungen ausführt.
Mit dem JDBCTemplate bietet Spring eine Klasse, die SQL-Anweisungen ausführt.
(Bild: Spring.io)

In den eingangs erwähnten Fällen ist es oft einfacher, auf die „Java Database Connectivity“-Schnittstelle, besser bekannt unter dem Kürzel JDBC zurückzugreifen. Die API stammt aus den Anfangstagen von Java und gilt als eher umständlich. Für eine einfache Abfrage ist eine ganze Reihe von Schritten zu programmieren:

  • Registrieren eines Treibers
  • Öffnen einer Connection mithilfe des Driver-Managers
  • Erzeugen eines Statements
  • Ausführen der Query
  • Iterieren über das Result-Set
  • Schließen der Connection

Glücklicherweise erleichtert Spring diese Mühsal durch das JDBCTemplate. Dabei handelt es sich um eine Klasse, die SQL-Anweisungen ausführt und die Ergebnisse zum Beispiel mit Unterstützung verschiedener Hilfsklassen in Objekte umwandeln kann.

Um das Öffnen und Schließen von Connections und das Erzeugen von Statements kümmert sich die Klasse ebenfalls. Außerdem wandelt sie die SQLException in eine Runtime-Exception um, die besser mit Spring zusammenarbeitet.

„Spring Boot“-Tutorial
Bildergalerie mit 22 Bildern

Vorbereitungen

Grundlage hier ist ein mit dem Spring Initializr angelegtes Projekt, dem die Starter-Dependencies H2 Database, JDBC API, Spring Web und – aus Gründen der Bequemlichkeit Lombok zugefügt wurden:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

Als Datenobjekt dient hier wieder die einfache Klasse Person, die auch schon in anderen Beiträgen der Reihe verwendet wurde. Die Getter und Setter werden mit der Lombok-Annotation @Data erzeugt. Ebenfalls von Lombok stammt die Annotation @Builder, mit der sich Personen effizient mit dem Builder-Pattern anlegen lassen:

@Data
@Builder
public class Person {
   private Long id;
   private String firstname;
   private String lastname;
}

Das zugehörige Datenbankschema für die H2-Datenbank wird in der Datei schema.sql in src/main/resources hinterlegt. Spring Boot liest die Datei aus und legt die Tabelle automatisch an:

CREATE TABLE person (
   id INTEGER NOT NULL AUTO_INCREMENT,
   firstname VARCHAR(128) NOT NULL,
      lastname VARCHAR(128) NOT NULL,
   PRIMARY KEY (id)
);

Ebenfalls unter src/main/resources liegt die Konfigurationsdatei application.properties. In ihr wird die Web-Konsole der H2-DB aktiviert und auf In-Memory-Betrieb umgestellt:

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb

Die Tabelle Person wurde angelegt, enthält aber noch keine Daten.
Die Tabelle Person wurde angelegt, enthält aber noch keine Daten.
(Bild: Koller)

Danach ist die Konsole der Datenbank unter der URL http://localhost:8080/h2-console/ erreichbar. Die korrekte JDBC-URL ist jdbc:h2:mem:testdb, ein Passwort ist nicht erforderlich. Wie der Screenshot zeigt, wurde die Tabelle Person angelegt. Sie enthält aber noch keine Daten. Testdaten kann man entweder mithilfe der H2-Konsole von Hand eingeben oder in der Datei data.sql in src/main/ressources hinterlegen:

INSERT INTO person VALUES (1, 'Max', 'Mustermann');
INSERT INTO person VALUES (2, 'Ute', 'Musterfrau');

Nach einem Neustart der Anwendung sind diese in der Konsole sichtbar.

Vom Datensatz zum Objekt: RowMapper

JDBCTemplate bietet verschiedene Wege an, die abgefragten Daten zu verarbeiten. Hier soll ein Person-Objekt erzeugt werden. Das geschieht mithilfe eines RowMappers. Das Interface enthält die Methode mapRow(ResultSet rs, int rowNum), in der das Umfüllen der Daten aus dem ResultSet in ein Person-Objekt implementiert wird:

public class PersonRowMapper implements RowMapper<Person> {   @Override
   public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
      Person person = Person.builder()
         .id(rs.getLong("ID"))
         .firstname(rs.getString("firstname"))
         .lastname(rs.getString("lastname"))
         .build();
      return person;
   }
}

JDBCTemplate

Damit ist alles beisammen, um die Datenbankabfrage mit JDBCTemplate auszuführen. Hier erfolgt das aus Platzgründen direkt in einer Controller-Klasse, in die eine JDBCTemplate-Instanz injiziert wird:

@Controller
public class JDBCTemplateController {
   @Autowired
   JdbcTemplate jdbcTemplate;
   @GetMapping("/{id}")
   @ResponseBody
   public String home(@PathVariable Long id) {
      Person person = jdbcTemplate.queryForObject("SELECT * FROM Person WHERE Id = ?", new PersonRowMapper(), new Object[] {id});
      return person.toString();
   }
}

Die angefragte Person mit der id=1 wird im Browser angezeigt.
Die angefragte Person mit der id=1 wird im Browser angezeigt.
(Bild: Koller)

Der gemappten URL / wird die Person-Id als Teil der URL mitgegeben. Der Aufruf jdbcTemplate.queryForObject() bringt dann alles zusammen: Die Query, den RowMapper und die übergebene Id, die als Query-Parameter anstelle des Fragezeichens eingesetzt wird. Die Rückgabe der Methode, also das erhaltene Person-Objekt, wird hier einfach in den Body der HTTP-Response geschrieben.

„Spring Boot“-Tutorial
Bildergalerie mit 22 Bildern

Ein Aufruf von http://localhost:8080/1 im Browser zeigt deshalb die Daten von Max Mustermann an. In der Praxis würde man das Person-Objekt beispielsweise an eine Thymeleaf-View weitergeben und dort mit HTML und CSS visualisieren. Für diesen Beitrag führt das aber zu weit.

Das kleine Beispiel verdeutlich die Leistungsfähigkeit von JDBCTemplate. Der Boilerplate-Code zum Öffnen der Datenbank-Connection, dem Erstellen des Statements und Behandeln der IOException entfällt komplett. Der Datenbankzugriff wird auf ein einziges Statement reduziert und lässt nun gut erkennen, worum es eigentlich geht.

JDBCTemplate kann aber noch einiges mehr. Zur Verarbeitung mit dem RowMapper existieren Alternativen und natürlich kann man auch Datensätze zufügen, aktualisieren oder löschen. Mehr dazu im nächsten Teil der Reihe.

(ID:47395355)