1. 概述

在本教程中,我们将通过实际应用场景来了解 Spring JDBC 模块的使用方法。

Spring JDBC 中的所有类都分为四个独立的包:

  • core — JDBC 的核心功能。该包下的一些重要类包括 JdbcTemplateSimpleJdbcInsertSimpleJdbcCallNamedParameterJdbcTemplate
  • datasource — 访问数据源的工具类。它还提供了多种数据源实现,用于在 Jakarta EE 容器外测试 JDBC 代码
  • object — 以面向对象的方式访问数据库。它允许运行查询并将结果作为业务对象返回。它还会将查询结果映射到业务对象的列和属性之间
  • support — 为 coreobject 包下的类提供支持,例如提供 SQLException 转换功能

2. Maven 依赖

让我们将 spring-boot-starter-jdbcmysql-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 语句。这些类使用数据库元数据来构建基本查询。因此,SimpleJdbcInsertSimpleJdbcCall 类提供了运行插入和存储过程调用的更简单方法。

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

最后,我们可以使用 BeanPropertySqlParameterSourceMapSqlParameterSource 传递这些数据。

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。


原始标题:Spring JDBC Tutorial | Baeldung