Spring JDBC 的全面教程

1、概览

本文将带你全面了解 Spring JDBC 模块。

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

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

2、Maven 依赖

pom.xml 中添加 spring-boot-starter-jdbcmysql-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 语句的简便方法。这些类使用数据库元数据来构建基本查询。

SimpleJdbcInsertSimpleJdbcCall 类提供了运行 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());

同样的,也可以使用 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 的基本批处理操作

通过 JdbcTemplatebatchUpdate() 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 进行批处理操作

还可以使用 NamedParameterJdbcTemplatebatchUpdate() 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