1. Información general
En este artículo, veremos casos de uso prácticos del módulo Spring JDBC.
Todas las clases en Spring JDBC se dividen en cuatro paquetes separados:
- core : la funcionalidad principal de JDBC. Algunas de las clases importantes de este paquete incluyen JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall y NamedParameterJdbcTemplate .
- datasource : clases de utilidad para acceder a una fuente de datos. También tiene varias implementaciones de fuentes de datos para probar el código JDBC fuera del contenedor de Jakarta EE.
- objeto : acceso a la base de datos de forma orientada a objetos. Permite ejecutar consultas y devolver los resultados como un objeto de negocio. También asigna los resultados de la consulta entre las columnas y las propiedades de los objetos comerciales.
- soporte : clases de soporte para clases enpaquetes de núcleo y objetos . Por ejemplo, proporciona lafuncionalidad de traducción SQLException .
2. Configuración
Para empezar, comencemos con una configuración simple de la fuente de datos (usaremos una base de datos MySQL para este ejemplo):
@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; } }
Alternativamente, también podemos hacer un buen uso de una base de datos incrustada para desarrollo o pruebas; aquí hay una configuración rápida que crea una instancia de la base de datos incrustada H2 y la rellena previamente con scripts SQL simples:
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:jdbc/schema.sql") .addScript("classpath:jdbc/test-data.sql").build(); }
Finalmente, lo mismo se puede hacer, por supuesto, usando la configuración XML para la fuente de datos :
3. JdbcTemplate y consultas en ejecución
3.1. Consultas básicas
La plantilla JDBC es la API principal a través de la cual accederemos a la mayoría de las funciones que nos interesan:
- creación y cierre de conexiones
- ejecutar declaraciones y llamadas a procedimientos almacenados
- iterando sobre ResultSet y devolviendo resultados
En primer lugar, comencemos con un ejemplo simple para ver lo que puede hacer JdbcTemplate :
int result = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM EMPLOYEE", Integer.class);
y también aquí hay un INSERTAR simple:
public int addEmplyee(int id) { return jdbcTemplate.update( "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA"); }
Observe la sintaxis estándar de proporcionar parámetros, utilizando el carácter `?`. A continuación, veamos una alternativa a esta sintaxis.
3.2. Consultas con parámetros con nombre
Para obtener soporte para parámetros con nombre , usaremos la otra plantilla JDBC proporcionada por el marco: NamedParameterJdbcTemplate .
Además, esto envuelve el JbdcTemplate y proporciona una alternativa a la sintaxis tradicional usando “ ? ”Para especificar parámetros. Bajo el capó, sustituye los parámetros nombrados por JDBC "?" marcador de posición y delegados al JDCTemplate envuelto para ejecutar las consultas:
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1); return namedParameterJdbcTemplate.queryForObject( "SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);
Observe cómo estamos usando MapSqlParameterSource para proporcionar los valores para los parámetros nombrados.
Por ejemplo, veamos el siguiente ejemplo que usa propiedades de un bean para determinar los parámetros nombrados:
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);
Observe cómo ahora estamos haciendo uso de las implementaciones de BeanPropertySqlParameterSource en lugar de especificar los parámetros nombrados manualmente como antes.
3.3. Asignación de resultados de consultas a objetos Java
Otra característica muy útil es la capacidad de mapear los resultados de las consultas a los objetos Java, mediante la implementación de la interfaz RowMapper .
Por ejemplo, para cada fila devuelta por la consulta, Spring usa el mapeador de filas para completar el 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; } }
Posteriormente, ahora podemos pasar el asignador de filas a la API de consulta y obtener objetos Java completamente poblados:
String query = "SELECT * FROM EMPLOYEE WHERE ID = ?"; Employee employee = jdbcTemplate.queryForObject( query, new Object[] { id }, new EmployeeRowMapper());
4. Traducción de excepción
Spring viene con su propia jerarquía de excepciones de datos lista para usar, con DataAccessException como la excepción raíz, y le traduce todas las excepciones sin procesar subyacentes.
Y así mantenemos nuestra cordura al no tener que manejar excepciones de persistencia de bajo nivel y nos beneficiamos del hecho de que Spring envuelve las excepciones de bajo nivel en DataAccessException o una de sus subclases.
Además, esto mantiene el mecanismo de manejo de excepciones independiente de la base de datos subyacente que estamos usando.
Además, el SQLErrorCodeSQLExceptionTranslator predeterminado , también podemos proporcionar nuestra propia implementación de SQLExceptionTranslator .
Aquí hay un ejemplo rápido de una implementación personalizada, personalizando el mensaje de error cuando hay una violación de clave duplicada, lo que da como resultado el código de error 23505 cuando se usa 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; } }
Para usar este traductor de excepciones personalizado, debemos pasarlo al JdbcTemplate llamando al método setExceptionTranslator () :
CustomSQLErrorCodeTranslator customSQLErrorCodeTranslator = new CustomSQLErrorCodeTranslator(); jdbcTemplate.setExceptionTranslator(customSQLErrorCodeTranslator);
5. Operaciones JDBC con clases SimpleJdbc
Las clases SimpleJdbc proporcionan una forma sencilla de configurar y ejecutar sentencias SQL. Estas clases utilizan metadatos de la base de datos para crear consultas básicas. Las clases SimpleJdbcInsert y SimpleJdbcCall proporcionan una forma más sencilla de ejecutar llamadas a procedimientos almacenados y de inserción.
5.1. SimpleJdbcInsert
Echemos un vistazo a la ejecución de sentencias de inserción simples con una configuración mínima.
La instrucción INSERT se genera en función de la configuración de SimpleJdbcInsert y todo lo que necesitamos es proporcionar el nombre de la tabla, los nombres de las columnas y los valores.
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 NamedParameterJdbcTemplate – batchUpdate() 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
Eso es todo, con solo hacer estas configuraciones, nuestra aplicación está en funcionamiento y podemos usarla para otras operaciones de base de datos.
La configuración explícita que vimos en la sección anterior para una aplicación Spring estándar ahora se incluye como parte de la configuración automática de Spring Boot.
8. Conclusión
En este artículo, analizamos la abstracción de JDBC en Spring Framework, cubriendo las diversas capacidades proporcionadas por Spring JDBC con ejemplos prácticos.
Además, analizamos cómo podemos comenzar rápidamente con Spring JDBC usando un iniciador Spring Boot JDBC.
El código fuente de los ejemplos está disponible en GitHub.