# 3.1.2 使用 JdbcTemplate

在开始使用 JdbcTemplate 之前，需要将它添加到项目类路径中。这很容易通过添加 Spring Boot 的 JDBC starter 依赖来实现：

```markup
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
```

还需要一个存储数据的数据库。出于开发目的，嵌入式数据库也可以。我喜欢 H2 嵌入式数据库，所以我添加了以下依赖进行构建：

```markup
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
```

稍后，将看到如何配置应用程序来使用外部数据库。但是现在，让我们继续编写一个获取和保存 Ingredient 数据的存储库。

**定义 JDBC 存储库**

Ingredient repository 需要执行以下操作：

* 查询所有的 Ingredient 使之变成一个 Ingredient 的集合对象
* 通过它的 id 查询单个 Ingredient
* 保存一个 Ingredient 对象

以下 IngredientRepository 接口将这三种操作定义为方法声明：

```java
package tacos.data;
​
import tacos.Ingredient;
​
public interface IngredientRepository {
    
    Iterable<Ingredient> findAll();
    
    Ingredient findOne(String id);
    
    Ingredient save(Ingredient ingredient);
}
```

尽管该接口体现了需要 Ingredient repository 做的事情的本质，但是仍然需要编写一个使用 JdbcTemplate 来查询数据库的 IngredientRepository 的实现。下面的程序清单是编写实现的第一步。

{% code title="程序清单 3.4 使用 JdbcTemplate 开始编写 Ingredient repository" %}

```java
package tacos.data;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
​
import tacos.Ingredient;
​
@Repository
public class JdbcIngredientRepository implements IngredientRepository {
    
    private JdbcTemplate jdbc;
    
    @Autowired
    public JdbcIngredientRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }
    
    ...
​
}
```

{% endcode %}

JdbcIngredientRepository 使用 @Repository 进行了注解。这个注解是 Spring 定义的少数几个原型注解之一，包括 @Controller 和 @Component。通过使用 @Repository 对 JdbcIngredientRepository 进行注解，这样它就会由 Spring 组件在扫描时自动发现，并在 Spring 应用程序上下文中生成 bean 实例。

当 Spring 创建 JdbcIngredientRepository bean 时，通过 @Autowired 注解将 JdbcTemplate 注入到 bean 中。构造函数将 JdbcTemplate 分配给一个实例变量，该变量将在其他方法中用于查询和插入数据库。谈到那些其他方法，让我们来看看 findAll() 和 findById() 的实现。

{% code title="程序清单 3.5 使用 JdbcTemplate 查询数据库" %}

```java
@Override
public Iterable<Ingredient> findAll() {
    return jdbc.query("select id, name, type from Ingredient",
              this::mapRowToIngredient);
}
​
@Override
public Ingredient findOne(String id) {
    return jdbc.queryForObject(
        "select id, name, type from Ingredient where id=?",
        this::mapRowToIngredient, id);
}
​
private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)
    throws SQLException {
    return new Ingredient(
        rs.getString("id"),
        rs.getString("name"),
        Ingredient.Type.valueOf(rs.getString("type")));
}
```

{% endcode %}

findAll() 和 findById() 都以类似的方式使用 JdbcTemplate。期望返回对象集合的 findAll() 方法使用了 JdbcTemplate 的 query() 方法。query() 方法接受查询的 SQL 以及 Spring 的 RowMapper 实现，以便将结果集中的每一行映射到一个对象。findAll() 还接受查询中所需的所有参数的列表作为它的最后一个参数。但是，在本例中，没有任何必需的参数。

findById() 方法只期望返回单个成分对象，因此它使用 JdbcTemplate 的 queryForObject() 方法而不是 query()。queryForObject() 的工作原理与 query() 非常相似，只是它返回的是单个对象，而不是对象列表。在本例中，它给出了要执行的查询、一个 RowMapper 和要获取的 Ingredient 的 id，后者用于代替查询 SQL 中 的 `?`。

如程序清单 3.5 所示，findAll() 和 findById() 的 RowMapper 参数作为 mapRowToIngredient() 方法的方法引用。当使用 JdbcTemplate 作为显式 RowMapper 实现的替代方案时，使用 Java 8 的方法引用和 lambda 非常方便。但是，如果出于某种原因，想要或是需要一个显式的 RowMapper，那么 findAll() 的以下实现将展示如何做到这一点：

```java
@Override
public Ingredient findOne(String id) {
    return jdbc.queryForObject(
        "select id, name, type from Ingredient where id=?",
        new RowMapper<Ingredient>() {
            public Ingredient mapRow(ResultSet rs, int rowNum)
                throws SQLException {
                return new Ingredient(
                    rs.getString("id"),
                    rs.getString("name"),
                    Ingredient.Type.valueOf(rs.getString("type")));
            };
        }, id);
}
```

从数据库读取数据只是问题的一部分。在某些情况下，必须将数据写入数据库以便能够读取。因此，让我们来看看如何实现 save() 方法。

**插入一行**

JdbcTemplate 的 update() 方法可用于在数据库中写入或更新数据的任何查询。并且，如下面的程序清单所示，它可以用来将数据插入数据库。

{% code title="程序清单 3.6 使用 JdbcTemplate 插入数据" %}

```java
@Override
public Ingredient save(Ingredient ingredient) {
    jdbc.update(
        "insert into Ingredient (id, name, type) values (?, ?, ?)",
        ingredient.getId(),
        ingredient.getName(),
        ingredient.getType().toString());
    return ingredient;
}
```

{% endcode %}

因为没有必要将 ResultSet 数据映射到对象，所以 update() 方法要比 query() 或 queryForObject() 简单得多。它只需要一个包含 SQL 的字符串来执行，以及为任何查询参数赋值。在本例中，查询有三个参数，它们对应于 save() 方法的最后三个参数，提供了 Ingredient 的 id、name 和 type。

完成了 JdbcIngredientRepository后，现在可以将其注入到 DesignTacoController 中，并使用它来提供一个 Ingredient 对象列表，而不是使用硬编码的值（正如第 2 章中所做的那样）。DesignTacoController 的变化如下所示。

{% code title="程序清单 3.7 在控制器中注入并使用 repository" %}

```java
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
    
    private final IngredientRepository ingredientRepo;
    
    @Autowired
    public DesignTacoController(IngredientRepository ingredientRepo) {
        this.ingredientRepo = ingredientRepo;
    }
    
    @GetMapping
    public String showDesignForm(Model model) {
        List<Ingredient> ingredients = new ArrayList<>();
        ingredientRepo.findAll().forEach(i -> ingredients.add(i));
        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                               filterByType(ingredients, type));
        }
        return "design";
    }
    
    ...
    
}
```

{% endcode %}

请注意，showDesignForm() 方法的第 2 行现在调用了注入的 IngredientRepository 的 findAll() 方法。findAll() 方法从数据库中提取所有 Ingredient，然后将它们对应到到模型的不同类型中。

几乎已经准备好启动应用程序并尝试这些更改了。但是在开始从查询中引用的 Ingredient 表读取数据之前，可能应该创建这个表并写一些 Ingredient 数据进去。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v5/di-3-zhang-chu-li-shu-ju/3.1-shi-yong-jdbc-du-xie-shu-ju/3.1.2-shi-yong-jdbctemplate.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
