Frühling JDBC

1. Übersicht

In diesem Artikel werden praktische Anwendungsfälle des Spring JDBC-Moduls behandelt.

Alle Klassen in Spring JDBC sind in vier separate Pakete unterteilt:

  • Kern - die Kernfunktionalität von JDBC. Einige der wichtigen Klassen in diesem Paket umfassen JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall und NamedParameterJdbcTemplate .
  • Datenquelle - Dienstprogrammklassen für den Zugriff auf eine Datenquelle. Es verfügt auch über verschiedene Datenquellenimplementierungen zum Testen von JDBC-Code außerhalb des Jakarta EE-Containers.
  • Objekt - DB-Zugriff objektorientiert. Es ermöglicht die Ausführung von Abfragen und die Rückgabe der Ergebnisse als Geschäftsobjekt. Außerdem werden die Abfrageergebnisse zwischen den Spalten und Eigenschaften von Geschäftsobjekten zugeordnet.
  • support - Unterstützungsklassen für Klassen unter Kern- und Objektpaketen . ZB bietet die SQLException- Übersetzungsfunktion.

2. Konfiguration

Beginnen wir mit einer einfachen Konfiguration der Datenquelle (für dieses Beispiel verwenden wir eine MySQL-Datenbank):

@Configuration @ComponentScan("com.baeldung.jdbc") public class SpringJdbcConfig { @Bean public DataSource mysqlDataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/springjdbc"); dataSource.setUsername("guest_user"); dataSource.setPassword("guest_password"); return dataSource; } }

Alternativ können wir auch eine eingebettete Datenbank für die Entwicklung oder das Testen gut nutzen. Hier ist eine schnelle Konfiguration, mit der eine Instanz der eingebetteten H2-Datenbank erstellt und mit einfachen SQL-Skripten vorab gefüllt wird:

@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:jdbc/schema.sql") .addScript("classpath:jdbc/test-data.sql").build(); } 

Schließlich kann das Gleiche natürlich mithilfe der XML-Konfiguration für die Datenquelle erfolgen :

3. Die JdbcTemplate- und Running-Abfragen

3.1. Grundlegende Abfragen

Die JDBC-Vorlage ist die Haupt-API, über die wir auf die meisten Funktionen zugreifen können, an denen wir interessiert sind:

  • Erstellen und Schließen von Verbindungen
  • Ausführen von Anweisungen und Aufrufen gespeicherter Prozeduren
  • Durchlaufen des ResultSet und Zurückgeben von Ergebnissen

Beginnen wir zunächst mit einem einfachen Beispiel, um zu sehen, was die JdbcTemplate leisten kann:

int result = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM EMPLOYEE", Integer.class); 

und auch hier ist ein einfaches INSERT:

public int addEmplyee(int id) { return jdbcTemplate.update( "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA"); }

Beachten Sie die Standardsyntax für die Bereitstellung von Parametern - unter Verwendung des Zeichens `?`. Weiter - schauen wir uns eine Alternative zu dieser Syntax an.

3.2. Abfragen mit benannten Parametern

Um Unterstützung für benannte Parameter zu erhalten , verwenden wir die andere vom Framework bereitgestellte JDBC-Vorlage - die NamedParameterJdbcTemplate .

Darüber hinaus wird die JbdcTemplate umschlossen und bietet eine Alternative zur herkömmlichen Syntax mit „ ? ”, Um Parameter anzugeben. Unter der Haube werden die genannten Parameter durch JDBC "?" Platzhalter und Delegaten an das umschlossene JDCTemplate , um die Abfragen auszuführen:

SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1); return namedParameterJdbcTemplate.queryForObject( "SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);

Beachten Sie, wie wir die MapSqlParameterSource verwenden , um die Werte für die genannten Parameter bereitzustellen.

Schauen wir uns zum Beispiel das folgende Beispiel an, das Eigenschaften einer Bean verwendet, um die benannten Parameter zu bestimmen:

Employee employee = new Employee(); employee.setFirstName("James"); String SELECT_BY_ID = "SELECT COUNT(*) FROM EMPLOYEE WHERE FIRST_NAME = :firstName"; SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(employee); return namedParameterJdbcTemplate.queryForObject( SELECT_BY_ID, namedParameters, Integer.class);

Beachten Sie, wie wir jetzt die BeanPropertySqlParameterSource- Implementierungen verwenden, anstatt die genannten Parameter wie zuvor manuell anzugeben.

3.3. Zuordnen von Abfrageergebnissen zu Java-Objekten

Eine weitere sehr nützliche Funktion ist die Möglichkeit, Abfrageergebnisse Java-Objekten zuzuordnen - durch Implementierung der RowMapper- Schnittstelle.

Beispiel: Für jede von der Abfrage zurückgegebene Zeile verwendet Spring den Zeilen-Mapper, um die Java-Bean zu füllen:

public class EmployeeRowMapper implements RowMapper { @Override public Employee mapRow(ResultSet rs, int rowNum) throws SQLException { Employee employee = new Employee(); employee.setId(rs.getInt("ID")); employee.setFirstName(rs.getString("FIRST_NAME")); employee.setLastName(rs.getString("LAST_NAME")); employee.setAddress(rs.getString("ADDRESS")); return employee; } }

Anschließend können wir den Zeilen-Mapper an die Abfrage-API übergeben und vollständig ausgefüllte Java-Objekte abrufen:

String query = "SELECT * FROM EMPLOYEE WHERE ID = ?"; Employee employee = jdbcTemplate.queryForObject( query, new Object[] { id }, new EmployeeRowMapper());

4. Ausnahmeübersetzung

Spring wird standardmäßig mit einer eigenen Datenausnahmehierarchie geliefert - mit DataAccessException als Root-Ausnahme - und übersetzt alle zugrunde liegenden Rohausnahmen in diese.

Daher behalten wir unsere Vernunft bei, indem wir keine Persistenzausnahmen auf niedriger Ebene behandeln müssen, und profitieren von der Tatsache, dass Spring die Ausnahmen auf niedriger Ebene in DataAccessException oder einer seiner Unterklassen umschließt .

Dadurch bleibt der Mechanismus zur Ausnahmebehandlung unabhängig von der zugrunde liegenden Datenbank, die wir verwenden.

Außerdem können wir mit dem Standard- SQLErrorCodeSQLExceptionTranslator auch unsere eigene Implementierung von SQLExceptionTranslator bereitstellen .

Hier ist ein kurzes Beispiel für eine benutzerdefinierte Implementierung, bei der die Fehlermeldung angepasst wird, wenn eine doppelte Schlüsselverletzung vorliegt, die bei Verwendung von H2 zum Fehlercode 23505 führt:

public class CustomSQLErrorCodeTranslator extends SQLErrorCodeSQLExceptionTranslator { @Override protected DataAccessException customTranslate(String task, String sql, SQLException sqlException) { if (sqlException.getErrorCode() == 23505) { return new DuplicateKeyException( "Custom Exception translator - Integrity constraint violation.", sqlException); } return null; } }

To use this custom exception translator, we need to pass it to the JdbcTemplate by calling setExceptionTranslator() method:

CustomSQLErrorCodeTranslator customSQLErrorCodeTranslator = new CustomSQLErrorCodeTranslator(); jdbcTemplate.setExceptionTranslator(customSQLErrorCodeTranslator);

5. JDBC Operations Using SimpleJdbc Classes

SimpleJdbc classes provide an easy way to configure and execute SQL statements. These classes use database metadata to build basic queries. SimpleJdbcInsert and SimpleJdbcCall classes provide an easier way to execute insert and stored procedure calls.

5.1. SimpleJdbcInsert

Let's take a look at executing simple insert statements with minimal configuration.

The INSERT statement is generated based on the configuration of SimpleJdbcInsert and all we need is to provide the Table name, Column names and values.

First, let's create a SimpleJdbcInsert:

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE");

Next, let's provide the Column names and values, and execute the operation:

public int addEmplyee(Employee emp) { Map parameters = new HashMap(); parameters.put("ID", emp.getId()); parameters.put("FIRST_NAME", emp.getFirstName()); parameters.put("LAST_NAME", emp.getLastName()); parameters.put("ADDRESS", emp.getAddress()); return simpleJdbcInsert.execute(parameters); }

Further, to allow the database to generate the primary key, we can make use of the executeAndReturnKey() API; we'll also need to configure the actual column that is auto-generated:

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource) .withTableName("EMPLOYEE") .usingGeneratedKeyColumns("ID"); Number id = simpleJdbcInsert.executeAndReturnKey(parameters); System.out.println("Generated id - " + id.longValue());

Finally – we can also pass in this data by using the BeanPropertySqlParameterSource and MapSqlParameterSource.

5.2. Stored Procedures With SimpleJdbcCall

Also, let's take a look at executing stored procedures – we'll make use of the SimpleJdbcCall abstraction:

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource) .withProcedureName("READ_EMPLOYEE"); 
public Employee getEmployeeUsingSimpleJdbcCall(int id) { SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id); Map out = simpleJdbcCall.execute(in); Employee emp = new Employee(); emp.setFirstName((String) out.get("FIRST_NAME")); emp.setLastName((String) out.get("LAST_NAME")); return emp; }

6. Batch Operations

Another simple use case – batching multiple operations together.

6.1. Basic Batch Operations Using JdbcTemplate

Using JdbcTemplate, Batch Operations can be executed via the batchUpdate() API.

The interesting part here is the concise but highly useful BatchPreparedStatementSetter implementation:

public int[] batchUpdateUsingJdbcTemplate(List employees) { return jdbcTemplate.batchUpdate("INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setInt(1, employees.get(i).getId()); ps.setString(2, employees.get(i).getFirstName()); ps.setString(3, employees.get(i).getLastName()); ps.setString(4, employees.get(i).getAddress(); } @Override public int getBatchSize() { return 50; } }); }

6.2. Batch Operations Using NamedParameterJdbcTemplate

We also have the option of batching operations with the NamedParameterJdbcTemplatebatchUpdate() API.

This API is simpler than the previous one – no need to implement any extra interfaces to set the parameters, as it has an internal prepared statement setter to set the parameter values.

Instead, the parameter values can be passed to the batchUpdate() method as an array of SqlParameterSource.

SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(employees.toArray()); int[] updateCounts = namedParameterJdbcTemplate.batchUpdate( "INSERT INTO EMPLOYEE VALUES (:id, :firstName, :lastName, :address)", batch); return updateCounts;

7. Spring JDBC With Spring Boot

Spring Boot provides a starter spring-boot-starter-jdbc for using JDBC with relational databases.

As with every Spring Boot starter, this one also helps us in getting our application up and running quickly.

7.1. Maven Dependency

We'll need the spring-boot-starter-jdbc dependency as the primary one as well as a dependency for the database that we'll be using. In our case, this is MySQL:

 org.springframework.boot spring-boot-starter-jdbc   mysql mysql-connector-java runtime 

7.2. Configuration

Spring Boot configures the data source automatically for us. We just need to provide the properties in a properties file:

spring.datasource.url=jdbc:mysql://localhost:3306/springjdbc spring.datasource.username=guest_user spring.datasource.password=guest_password

Nur durch diese Konfigurationen ist unsere Anwendung betriebsbereit und wir können sie für andere Datenbankoperationen verwenden.

Die explizite Konfiguration, die wir im vorherigen Abschnitt für eine Standard-Spring-Anwendung gesehen haben, ist jetzt Teil der automatischen Spring Boot-Konfiguration.

8. Fazit

In diesem Artikel haben wir uns die JDBC-Abstraktion im Spring Framework angesehen und die verschiedenen Funktionen von Spring JDBC anhand praktischer Beispiele behandelt.

Außerdem haben wir untersucht, wie wir mit einem Spring Boot JDBC-Starter schnell mit Spring JDBC beginnen können.

Der Quellcode für die Beispiele ist auf GitHub verfügbar.