Printemps JDBC

1. Vue d'ensemble

Dans cet article, nous allons passer en revue des cas d'utilisation pratiques du module Spring JDBC.

Toutes les classes de Spring JDBC sont divisées en quatre packages distincts:

  • core - la fonctionnalité de base de JDBC. Certaines des classes importantes de ce package incluent JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall et NamedParameterJdbcTemplate .
  • datasource - classes utilitaires pour accéder à une source de données. Il dispose également de diverses implémentations de sources de données pour tester le code JDBC en dehors du conteneur Jakarta EE.
  • objet - Accès à la base de données d'une manière orientée objet. Il permet d'exécuter des requêtes et de renvoyer les résultats sous forme d'objet métier. Il mappe également les résultats de la requête entre les colonnes et les propriétés des objets métier.
  • support - classes de support pour les classes sous les packages core et object . Par exemple, fournit lafonctionnalité de traduction SQLException .

2. Configuration

Pour commencer, commençons par une configuration simple de la source de données (nous utiliserons une base de données MySQL pour cet exemple):

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

Alternativement, nous pouvons également faire bon usage d'une base de données intégrée pour le développement ou les tests - voici une configuration rapide qui crée une instance de base de données intégrée H2 et la pré-remplit avec de simples scripts SQL:

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

Enfin - la même chose peut, bien sûr, être faite en utilisant la configuration XML pour la source de données :

3. Le JdbcTemplate et les requêtes en cours d'exécution

3.1. Requêtes de base

Le modèle JDBC est l'API principale à travers laquelle nous accéderons à la plupart des fonctionnalités qui nous intéressent:

  • création et fermeture de connexions
  • exécution d'instructions et d'appels de procédure stockée
  • itération sur le ResultSet et renvoi des résultats

Tout d'abord, commençons par un exemple simple pour voir ce que le JdbcTemplate peut faire:

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

et voici aussi un simple INSERT:

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

Notez la syntaxe standard de la fourniture de paramètres - en utilisant le caractère «?». Ensuite, regardons une alternative à cette syntaxe.

3.2. Requêtes avec des paramètres nommés

Pour obtenir la prise en charge des paramètres nommés , nous utiliserons l'autre modèle JDBC fourni par le framework - le NamedParameterJdbcTemplate .

De plus, cela encapsule le JbdcTemplate et fournit une alternative à la syntaxe traditionnelle utilisant « ? »Pour spécifier les paramètres. Sous le capot, il remplace les paramètres nommés par JDBC "?" espace réservé et délègue au JDCTemplate encapsulé pour exécuter les requêtes:

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

Notez comment nous utilisons MapSqlParameterSource pour fournir les valeurs des paramètres nommés.

Par exemple, regardons l'exemple ci-dessous qui utilise les propriétés d'un bean pour déterminer les paramètres nommés:

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

Notez comment nous utilisons maintenant les implémentations BeanPropertySqlParameterSource au lieu de spécifier les paramètres nommés manuellement comme auparavant.

3.3. Mappage des résultats de la requête vers un objet Java

Une autre fonctionnalité très utile est la possibilité de mapper les résultats de la requête sur des objets Java - en implémentant l' interface RowMapper .

Par exemple - pour chaque ligne renvoyée par la requête, Spring utilise le mappeur de lignes pour remplir le bean java:

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

Par la suite, nous pouvons maintenant transmettre le mappeur de lignes à l'API de requête et obtenir des objets Java entièrement remplis:

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

4. Traduction d'exception

Spring est livré avec sa propre hiérarchie d'exceptions de données prête à l' emploi - avec DataAccessException comme exception racine - et il y traduit toutes les exceptions brutes sous-jacentes.

And so we keep our sanity by not having to handle low-level persistence exceptions and benefit from the fact that Spring wraps the low-level exceptions in DataAccessException or one of its sub-classes.

Also, this keeps the exception handling mechanism independent of the underlying database we are using.

Besides, the default SQLErrorCodeSQLExceptionTranslator, we can also provide our own implementation of SQLExceptionTranslator.

Here's a quick example of a custom implementation, customizing the error message when there is a duplicate key violation, which results in error code 23505 when using H2:

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

Voilà, rien qu'en effectuant ces configurations uniquement, notre application est opérationnelle et nous pouvons l'utiliser pour d'autres opérations de base de données.

La configuration explicite que nous avons vue dans la section précédente pour une application Spring standard est désormais incluse dans le cadre de la configuration automatique de Spring Boot.

8. Conclusion

Dans cet article, nous avons examiné l'abstraction JDBC dans Spring Framework, couvrant les différentes fonctionnalités fournies par Spring JDBC avec des exemples pratiques.

Nous avons également examiné comment démarrer rapidement avec Spring JDBC à l'aide d'un démarreur Spring Boot JDBC.

Le code source des exemples est disponible à l'adresse over sur GitHub.