Spring JDBC 的全面教程
1、概览
本文将带你全面了解 Spring JDBC 模块。
Spring JDBC 中的所有类都分为四个独立的包:
core
— JDBC 的核心功能。该软件包下的一些重要类包括JdbcTemplate
、SimpleJdbcInsert
、SimpleJdbcCall
和NamedParameterJdbcTemplate
。datasource
— 用于访问数据源的工具类。它还有各种数据源实现,用于在 Jakarta EE 容器外测试 JDBC 代码。object
— 以面向对象的方式访问数据库。它允许运行查询并将结果作为业务对象返回。它还能在业务对象的列和属性之间映射查询结果。support
—core
包和object
包下的类的支持类,例如提供SQLException
翻译功能。
2、Maven 依赖
在 pom.xml
中添加 spring-boot-starter-jdbc 和 mysql-connector-j:
<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>
然后,在 pom.xml
中添加 h2 依赖:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>test</scope>
</dependency>
H2 数据库是一个嵌入式数据库,可用于快速原型开发。
3、配置
在 Spring 中配置数据源有两种主要方法:使用 properties 文件或使用基于 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 嵌入式数据库实例,并指定初始化 Schema 和测试数据的脚本:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2) // H2 数据库
.addScript("classpath:jdbc/schema.sql") // schema 脚本
.addScript("classpath:jdbc/test-data.sql") // 测试数据脚本
.build();
}
更推荐在 application.properties
文件中配置数据源。
4、JdbcTemplate
来看看 JdbcTemplate
基本用法。
4.1、基础查询
JdbcTemplate
是主要的 API:
- 创建和关闭连接。
- 运行语句和调用存储过程。
- 迭代
ResultSet
并返回结果。
首先,来看一个简单的查询:
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");
}
注意,SQL 语句中使用了标准的 ?
字符来进行参数占位。
4.2、使用命名参数进行查询
要使用命名参数,这需要使用框架提供的另一个 JDBC template - NamedParameterJdbcTemplate
。
它封装了 JbdcTemplate
,在底层,它将命名的参数替换为 JDBC ?
占位符,示例如下:
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1);
return namedParameterJdbcTemplate.queryForObject(
"SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);
如上,在 SQL 中使用 :<name>
来命名占位参数的名称,并通过 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
把 Bean 作为命名参数的值。SQL 中的命名参数和 Bean 中的属性名称一一对应。
4.3、映射查询结果到对象
另一个非常有用的功能是通过 RowMapper
接口将查询结果映射到 Java 对象。
对于结果集中的每一行,Spring 都会调用 mapper 来创建和填充 Java Bean:
// 创建 EmployeeRowMapper,实现 RowMapper 接口
public class EmployeeRowMapper implements RowMapper<Employee> {
/*
把结果集转换为 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;
}
}
然后,就可以将行 mapper
实例传递给查询 API,从而获得填充了完整数据的 Java 对象:
String query = "SELECT * FROM EMPLOYEE WHERE ID = ?";
Employee employee = jdbcTemplate.queryForObject(query, new EmployeeRowMapper(), id);
5、异常翻译
Spring 自带数据异常层次结构(根异常是 DataAccessException
),并将所有底层原始异常转化为数据异常。
DataAccessException
中封装了底层的异常,通过这层抽象,可以使异常处理机制独立于底层的数据库。
默认异常翻译器是 SQLErrorCodeSQLExceptionTranslator
,我们可以继承它以进行扩展。
下面是一个自定义实现的简单示例 - 自定义 KEY 重复异常时的错误信息,使用 H2 时会返回 error code 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
类提供了运行 INSERT
和存储过程调用的简便方法。
6.1、SimpleJdbcInsert
来看看如何以最少的配置运行简单的插入语句。
首先,创建 SimpleJdbcInsert
:
SimpleJdbcInsert simpleJdbcInsert =
new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE");
INSERT
语句是根据 SimpleJdbcInsert
的配置生成的。我们只需提供表名、列名和值:
然后,提供列名和值并执行操作:
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"); // 返回自增 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 进行批处理:
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;
}
});
}
如上,通过 BatchPreparedStatementSetter
来提供批处理参数。
7.2、使用 NamedParameterJdbcTemplate 进行批处理操作
还可以使用 NamedParameterJdbcTemplate
的 batchUpdate()
API 进行批处理操作。
该 API 比前一个更简单,不需要实现任何额外的接口来设置参数,它内部有一个 prepared statement setter 来设置参数值。
// 根据一组数据,创建 SqlParameterSource 数组
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(employees.toArray());
// 执行批处理
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"INSERT INTO EMPLOYEE VALUES (:id, :firstName, :lastName, :address)", batch);
return updateCounts;
如上,把 SqlParameterSource
参数值数组传递给 batchUpdate()
方法即可。
8、总结
本文介绍了 Spring 框架中的 JDBC 抽象,并通过实际示例介绍了 Spring JDBC 提供的各种功能。
Ref:https://www.baeldung.com/spring-jdbc-jdbctemplate