Spring JDBC

2023/05/18

1. 概述

在本教程中,我们将介绍 Spring JDBC 模块的实际用例。

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

  • 核心——JDBC 的核心功能。这个包下的一些重要类包括JdbcTemplate、 SimpleJdbcInsert、 SimpleJdbcCall和NamedParameterJdbcTemplate。

  • 数据源——访问数据源的实用类。它还具有用于在 Jakarta EE 容器外部测试 JDBC 代码的各种数据源实现。

  • object — 以面向对象的方式访问数据库。它允许运行查询并将结果作为业务对象返回。它还在业务对象的列和属性之间映射查询结果。

  • support—核心和对象包下类的支持类,例如,提供SQLException翻译功能

2.配置

让我们从数据源的一些简单配置开始。

我们将使用 MySQL 数据库:

@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;
    }
}

或者,我们也可以利用嵌入式数据库进行开发或测试。

这是一个快速配置,它创建一个 H2 嵌入式数据库实例并使用简单的 SQL 脚本预填充它:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
      .setType(EmbeddedDatabaseType.H2)
      .addScript("classpath:jdbc/schema.sql")
      .addScript("classpath:jdbc/test-data.sql").build();
}

最后,可以使用 XML 配置数据源来完成相同的操作:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
  destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/springjdbc"/>
    <property name="username" value="guest_user"/>
    <property name="password" value="guest_password"/>
</bean>

3. JdbcTemplate和运行查询

3.1. 基本查询

JDBC 模板是主要的 API,我们将通过它访问我们感兴趣的大部分功能:

  • 创建和关闭连接
  • 运行语句和存储过程调用
  • 遍历ResultSet并返回结果

首先,让我们从一个简单的例子开始,看看JdbcTemplate可以做什么:

int result = jdbcTemplate.queryForObject(
    "SELECT COUNT() FROM EMPLOYEE", Integer.class);

这是一个简单的插入:

public int addEmplyee(int id) {
    return jdbcTemplate.update(
      "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA");
}

请注意使用?提供参数的标准语法。特点。

接下来,让我们看看这种语法的替代方法。

3.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实现而不是像以前那样手动指定命名参数。

3.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 Object[] { id }, new EmployeeRowMapper());

4.异常翻译

Spring 附带了自己开箱即用的数据异常层次结构——DataAccessException作为根异常——并将所有底层原始异常转换为它。

因此,我们通过不处理低级持久性异常来保持理智。我们还受益于 Spring 将低级异常包装在DataAccessException或其子类之一中这一事实。

这也使异常处理机制独立于我们正在使用的底层数据库。

除了默认的SQLErrorCodeSQLExceptionTranslator之外,我们还可以提供自己的SQLExceptionTranslator实现。

下面是自定义实现的一个简单示例 — 在存在重复键违规时自定义错误消息,这会在使用 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);

5. 使用 SimpleJdbc 类的 JDBC 操作

SimpleJdbc类提供了一种配置和运行 SQL 语句的简单方法。这些类使用数据库元数据来构建基本查询。因此,SimpleJdbcInsert和SimpleJdbcCall类提供了一种更简单的方法来运行插入和存储过程调用。

5.1. 简单的JdbcInsert

让我们看一下以最少的配置运行简单的插入语句。

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传入这些数据。

5.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;
}

6.批量操作

另一个简单的用例是将多个操作一起批处理。

6.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;
            }
        });
}

6.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;

7. 使用Spring Boot的 Spring JDBC

Spring Boot 提供了一个 starter spring-boot-starter-jdbc用于将 JDBC 与关系数据库一起使用。

与每个Spring Boot启动器一样,这个启动器可以帮助我们快速启动和运行应用程序。

7.1. Maven 依赖

我们需要 spring-boot-starter-jdbc依赖作为主要依赖。我们还需要我们将使用的数据库的依赖项。在我们的例子中,这是MySQL:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

7.2. 配置

Spring Boot 会自动为我们配置数据源。我们只需要在属性文件中提供属性:

spring.datasource.url=jdbc:mysql://localhost:3306/springjdbc
spring.datasource.username=guest_user
spring.datasource.password=guest_password

就是这样。我们的应用程序仅通过进行这些配置即可启动并运行。我们现在可以将它用于其他数据库操作。

我们在上一节中看到的标准 Spring 应用程序的显式配置现在作为Spring Boot自动配置的一部分包含在内。

八. 总结

在本文中,我们研究了 Spring Framework 中的 JDBC 抽象。我们通过实际示例介绍了 Spring JDBC 提供的各种功能。

我们还研究了如何使用Spring BootJDBC starter 快速开始使用 Spring JDBC。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章