1. 概述
在本教程中,我们将通过实际应用场景来了解 Spring JDBC 模块的使用方法。
Spring JDBC 中的所有类都分为四个独立的包:
- core — JDBC 的核心功能。该包下的一些重要类包括 JdbcTemplate、SimpleJdbcInsert、SimpleJdbcCall 和 NamedParameterJdbcTemplate
- datasource — 访问数据源的工具类。它还提供了多种数据源实现,用于在 Jakarta EE 容器外测试 JDBC 代码
- object — 以面向对象的方式访问数据库。它允许运行查询并将结果作为业务对象返回。它还会将查询结果映射到业务对象的列和属性之间
- support — 为 core 和 object 包下的类提供支持,例如提供 SQLException 转换功能
2. Maven 依赖
让我们将 spring-boot-starter-jdbc 和 mysql-connector-j 添加到 pom.xml 中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
</dependency>
另外,让我们将 h2 依赖添加到 pom.xml 中:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>test</scope>
</dependency>
H2 数据库 是一个嵌入式数据库,适用于快速原型开发。
3. 配置
在 Spring 中配置数据源主要有两种方法:使用属性文件或使用基于 Java 的配置。
3.1. MySQL 配置
要配置数据源,让我们修改 application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/springjdbc
spring.datasource.username=guest_user
spring.datasource.password=guest_password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
这里我们配置了 MySQL 数据库的连接详细信息。现在可以使用它进行数据库操作。
值得注意的是,我们也可以将数据源配置为 bean:
@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;
}
}
但是,我们应该优先使用 application.properties 文件配置,因为它将配置与代码分离。
3.2. H2 数据库配置
或者,我们也可以充分利用嵌入式数据库进行开发或测试。在这种情况下,我们可以在 application.properties 文件中定义 H2 数据库连接详细信息:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.datasource.schema=classpath:jdbc/schema.sql
spring.datasource.data=classpath:jdbc/test-data.sql
或者,这里有一个快速配置,创建 H2 嵌入式数据库实例并使用简单的 SQL 脚本预填充它作为 bean:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:jdbc/schema.sql")
.addScript("classpath:jdbc/test-data.sql").build();
}
如果我们不想在 application.properties 文件中定义配置,可以使用这个配置。但是,通常更倾向于在 application.properties 中配置数据源。
值得注意的是,我们可以使用 Spring 配置文件 来管理项目中的多个配置。
4. JdbcTemplate 和运行查询
让我们探索 JdbcTemplate 的基本用法。
4.1. 基本查询
JDBC 模板是主要的 API,通过它我们可以访问大部分我们感兴趣的功能:
- 创建和关闭连接
- 运行语句和存储过程调用
- 遍历 ResultSet 并返回结果
首先,让我们从一个简单的示例开始,看看 JdbcTemplate 能做什么:
int result = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM EMPLOYEE", Integer.class);
这是一个简单的 INSERT:
public int addEmplyee(int id) {
return jdbcTemplate.update(
"INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA");
}
注意使用 ? 字符提供参数的标准语法。
接下来,让我们看看这种语法的替代方案。
4.2. 使用命名参数的查询
要获得命名参数支持,让我们使用框架提供的另一个 JDBC 模板 — NamedParameterJdbcTemplate。
它包装了 JbdcTemplate,并提供了使用 ? 指定参数的传统语法的替代方案。
在底层,它将命名参数替换为 JDBC ? 占位符,并委托给包装的 JDCTemplate 来运行查询:
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1);
return namedParameterJdbcTemplate.queryForObject(
"SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);
注意我们如何使用 MapSqlParameterSource 来为命名参数提供值。
让我们看看如何使用 bean 的属性来确定命名参数:
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);
注意我们现在使用 BeanPropertySqlParameterSource 实现而不是像以前那样手动指定命名参数。
4.3. 将查询结果映射到 Java 对象
另一个非常有用的功能是通过实现 RowMapper 接口将查询结果映射到 Java 对象。
例如,对于查询返回的每一行,Spring 使用行映射器来填充 java bean:
public class EmployeeRowMapper implements RowMapper<Employee> {
@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;
}
}
随后,我们现在可以将行映射器传递给查询 API 并获得完全填充的 Java 对象:
String query = "SELECT * FROM EMPLOYEE WHERE ID = ?";
Employee employee = jdbcTemplate.queryForObject(query, new EmployeeRowMapper(), id);
5. 异常转换
Spring 自带了其自己的数据异常层次结构 — 以 DataAccessException 作为根异常 — 并将所有底层的原始异常转换为它。
因此,我们通过不处理低级别的持久性异常来保持理智。我们还受益于 Spring 将低级别异常包装在 DataAccessException 或其子类之一中的事实。
这也使异常处理机制独立于我们使用的底层数据库。
除了默认的 SQLErrorCodeSQLExceptionTranslator,我们也可以自己实现。
这是一个自定义实现的快速示例 — 当存在重复键违规时自定义错误消息,在使用 H2 时会导致错误代码 23505:
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;
}
}
要使用这个自定义异常转换器,我们需要通过调用 setExceptionTranslator() 方法将其传递给 JdbcTemplate:
CustomSQLErrorCodeTranslator customSQLErrorCodeTranslator =
new CustomSQLErrorCodeTranslator();
jdbcTemplate.setExceptionTranslator(customSQLErrorCodeTranslator);
6. 使用 SimpleJdbc 类进行 JDBC 操作
SimpleJdbc 类提供了一种简单的方法来配置和运行 SQL 语句。这些类使用数据库元数据来构建基本查询。因此,SimpleJdbcInsert 和 SimpleJdbcCall 类提供了运行插入和存储过程调用的更简单方法。
6.1. SimpleJdbcInsert
让我们看看如何以最少的配置运行简单的插入语句。
INSERT 语句是基于 SimpleJdbcInsert 的配置生成的。 我们只需要提供表名、列名和值名。
首先,让我们创建一个 SimpleJdbcInsert:
SimpleJdbcInsert simpleJdbcInsert =
new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE");
接下来,让我们提供列名和值并运行操作:
public int addEmplyee(Employee emp) {
Map<String, Object> parameters = new HashMap<String, Object>();
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);
}
此外,我们可以使用 executeAndReturnKey() API 来允许数据库生成主键。我们还需要配置实际的自动生成列:
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("EMPLOYEE")
.usingGeneratedKeyColumns("ID");
Number id = simpleJdbcInsert.executeAndReturnKey(parameters);
System.out.println("Generated id - " + id.longValue());
最后,我们可以使用 BeanPropertySqlParameterSource 和 MapSqlParameterSource 传递这些数据。
6.2. 使用 SimpleJdbcCall 的存储过程
让我们也看看如何运行存储过程。
我们将使用 SimpleJdbcCall 抽象:
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("READ_EMPLOYEE");
public Employee getEmployeeUsingSimpleJdbcCall(int id) {
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);
Map<String, Object> out = simpleJdbcCall.execute(in);
Employee emp = new Employee();
emp.setFirstName((String) out.get("FIRST_NAME"));
emp.setLastName((String) out.get("LAST_NAME"));
return emp;
}
7. 批量操作
另一个简单的用例是将多个操作批量执行。
7.1. 使用 JdbcTemplate 的基本批量操作
使用 JdbcTemplate,批量操作可以通过 batchUpdate() API 运行。
这里有趣的部分是简洁但非常有用的 BatchPreparedStatementSetter 实现:
public int[] batchUpdateUsingJdbcTemplate(List<Employee> 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;
}
});
}
7.2. 使用 NamedParameterJdbcTemplate 的批量操作
我们还可以选择使用 NamedParameterJdbcTemplate 批量操作 — batchUpdate() API。
这个 API 比前一个更简单。因此,不需要实现任何额外的接口来设置参数,因为它有内部的预处理语句设置器来设置参数值。
相反,参数值可以作为 SqlParameterSource 数组传递给 batchUpdate() 方法。
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(employees.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"INSERT INTO EMPLOYEE VALUES (:id, :firstName, :lastName, :address)", batch);
return updateCounts;
8. 总结
在本文中,我们了解了 Spring Framework 中的 JDBC 抽象。我们通过实际示例介绍了 Spring JDBC 提供的各种功能。
我们还研究了如何使用 Spring Boot JDBC starter 快速开始使用 Spring JDBC。