Spring 实战(第五版)
  • Spring 实战(第 5 版)
  • 第一部分 Spring 基础
  • 第 1 章 Spring 入门
    • 1.1 什么是 Spring?
    • 1.2 初始化 Spring 应用程序
      • 1.2.1 使用 Spring Tool Suite 初始化 Spring 项目
      • 1.2.2 检查 Spring 项目结构
    • 1.3 编写 Spring 应用程序
      • 1.3.1 处理 web 请求
      • 1.3.2 定义视图
      • 1.3.3 测试控制器
      • 1.3.4 构建并运行应用程序
      • 1.3.5 了解 Spring Boot DevTools
      • 1.3.6 回顾
    • 1.4 俯瞰 Spring 风景线
      • 1.4.1 Spring 核心框架
      • 1.4.2 Spring Boot
      • 1.4.3 Spring Data
      • 1.4.4 Spring Security
      • 1.4.5 Spring Integration 和 Spring Batch
      • 1.4.6 Spring Cloud
    • 1.5 小结
  • 第 2 章 开发 Web 应用程序
    • 2.1 展示信息
      • 2.1.1 建立域
      • 2.1.2 创建控制器类
      • 2.1.3 设计视图
    • 2.2 处理表单提交
    • 2.3 验证表单输入
      • 2.3.1 声明验证规则
      • 2.3.2 在表单绑定时执行验证
      • 2.3.3 显示验证错误
    • 2.4 使用视图控制器
    • 2.5 选择视图模板库
      • 2.5.1 缓存模板
    • 2.6 小结
  • 第 3 章 处理数据
    • 3.1 使用 JDBC 读写数据
      • 3.1.1 为域适配持久化
      • 3.1.2 使用 JdbcTemplate
      • 3.1.3 定义模式并预加载数据
      • 3.1.4 插入数据
    • 3.2 使用 Spring Data JPA 持久化数据
      • 3.2.1 添加 Spring Data JPA 到数据库中
      • 3.2.2 注解域作为实体
      • 3.2.3 声明 JPA repository
      • 3.2.4 自定义 JPA repository
    • 3.3 小结
  • 第 4 章 Spring 安全
    • 4.1 启用 Spring Security
    • 4.2 配置 Spring Security
      • 4.2.1 内存用户存储
      • 4.2.2 基于 JDBC 的用户存储
      • 4.2.3 LDAP 支持的用户存储
      • 4.2.4 自定义用户身份验证
    • 4.3 保护 web 请求
      • 4.3.1 保护请求
      • 4.3.2 创建用户登录页面
      • 4.3.3 登出
      • 4.3.4 阻止跨站请求伪造攻击
    • 4.4 了解你的用户
    • 4.5 小结
  • 第 5 章 使用配置属性
    • 5.1 微调自动配置
      • 5.1.1 理解 Spring 环境抽象
      • 5.1.2 配置数据源
      • 5.1.3 配置嵌入式服务器
      • 5.1.4 配置日志
      • 5.1.5 使用特殊的属性值
    • 5.2 创建自己的配置属性
      • 5.2.1 定义配置属性持有者
      • 5.2.2 声明配置属性元数据
    • 5.3 使用 profile 文件进行配置
      • 5.3.1 定义特定 profile 的属性
      • 5.3.2 激活 profile 文件
      • 5.3.3 有条件地使用 profile 文件创建 bean
    • 5.4 小结
  • 第二部分 集成 Spring
  • 第 6 章 创建 REST 服务
    • 6.1 编写 RESTful 控制器
      • 6.1.1 从服务器获取数据
      • 6.1.2 向服务器发送数据
      • 6.1.3 更新服务器上的资源
      • 6.1.4 从服务器删除数据
    • 6.2 启用超媒体
      • 6.2.1 添加超链接
      • 6.2.2 创建资源装配器
      • 6.2.3 嵌套命名关系
    • 6.3 启用以数据为中心的服务
      • 6.3.1 调整资源路径和关系名称
      • 6.3.2 分页和排序
      • 6.3.3 添加用户端点
      • 6.3.4 向 Spring Data 端点添加用户超链接
    • 6.4 小结
  • 第 7 章 调用 REST 服务
    • 7.1 使用 RestTemplate 调用 REST 端点
      • 7.1.1 请求 GET 资源
      • 7.1.2 请求 PUT 资源
      • 7.1.3 请求 DELETE 资源
      • 7.1.4 请求 POST 资源
    • 7.2 使用 Traverson 引导 REST API
    • 7.3 小结
  • 第 8 章 发送异步消息
    • 8.1 使用 JMS 发送消息
      • 8.1.3 接收 JMS 消息
      • 8.1.2 使用 JmsTemplate 发送消息
      • 8.1.1 设置 JMS
    • 8.2 使用 RabbitMQ 和 AMQP
      • 8.2.1 添加 RabbitMQ 到 Spring 中
      • 8.2.2 使用 RabbitTemplate 发送消息
      • 8.2.3 从 RabbitMQ 接收消息
    • 8.3 使用 Kafka 发送消息
      • 8.3.1 在 Spring 中设置 Kafka
      • 8.3.2 使用 KafkaTemplate 发送消息
      • 8.3.3 编写 Kafka 监听器
    • 8.4 小结
  • 第 9 章 集成 Spring
    • 9.1 声明简单的集成流
      • 9.1.1 使用 XML 定义集成流
      • 9.1.2 在 Java 中配置集成流
      • 9.1.3 使用 Spring Integration 的 DSL 配置
    • 9.2 探索 Spring Integration
      • 9.2.1 消息通道
      • 9.2.2 过滤器
      • 9.2.3 转换器
      • 9.2.4 路由
      • 9.2.5 分割器
      • 9.2.6 服务激活器
      • 9.2.7 网关
      • 9.2.8 通道适配器
      • 9.2.9 端点模块
    • 9.3 创建 Email 集成流
    • 9.4 总结
  • 第三部分 响应式 Spring
  • 第 10 章 Reactor 介绍
    • 10.1 理解响应式编程
      • 10.1.1 定义响应式流
    • 10.2 Reactor
      • 10.2.1 图解响应式流
      • 10.2.2 添加 Reactor 依赖
    • 10.3 通用响应式操作实战
      • 10.3.1 创建响应式类型
      • 10.3.2 响应式类型结合
      • 10.3.3 转换和过滤响应式流
      • 10.3.4 对反应类型执行逻辑操作
    • 10.4 总结
  • 第 11 章 开发响应式 API
    • 11.1 使用 Spring WebFlux
      • 11.1.1 Spring WebFlux 介绍
      • 11.1.2 编写响应式 Controller
    • 11.2 定义函数式请求处理程序
    • 11.3 测试响应式 Controller
      • 11.3.1 测试 GET 请求
      • 11.3.2 测试 POST 请求
      • 11.3.3 使用线上服务器进行测试
    • 11.4 响应式消费 REST API
      • 11.4.1 通过 GET 方式获取资源
      • 11.4.2 通过 POST 方式发送资源
      • 11.4.3 删除资源
      • 11.4.4 处理请求错误
      • 11.4.5 请求转换
    • 11.5 保护响应式 web API
      • 11.5.1 配置响应式 Web 安全
      • 11.5.2 配置响应式用户信息服务
    • 11.6 总结
  • 第 12 章 响应式持久化数据
    • 12.1 理解 Spring Data 响应式历程
      • 12.1.1 Spring Data 响应式精髓
      • 12.1.2 在响应式与非响应式之间进行转换
      • 12.1.3 开发响应式库
    • 12.2 使用响应式 Cassandra 库
      • 12.2.1 开启 Spring Data Cassandra
      • 12.2.2 理解 Cassandra 数据模型
      • 12.2.3 Cassandra 持久化实体映射
      • 12.2.4 编写响应式 Cassandra 库
    • 12.3 编写响应式 MongoDB 库
      • 12.3.1 开启Spring Data MongonDB
      • 12.3.2 MongoDB 持久化实体映射
      • 12.3.3 编写响应式 MongoDB 库
    • 12.4 总结
  • 第四部分 云原生 Spring
  • 第 13 章 服务发现
    • 13.1深入思考微服务
    • 13.2 配置服务注册
      • 13.2.1 配置 Eureka
      • 13.2.2 扩展 Eureka
    • 13.3 注册并发现服务
      • 13.3.1 配置 Eureka 客户端属性
      • 13.3.2 消费服务
    • 13.4 总结
  • 第 14 章 配置管理
    • 14.1 共享配置
    • 14.2 运行配置服务器
      • 14.2.1 启动配置服务器
      • 14.2.2 填写配置库
    • 14.3 消费共享的配置
    • 14.4 服务应用程序和特定配置文件的属性
      • 14.4.1 服务特定应用程序的属性
      • 14.4.2 服务配置文件属性
    • 14.5 为配置的属性加密
      • 14.5.1 在 Git 中加密属性
      • 14.5.2 在 Vault 中存储密码
    • 14.6 远程刷新配置属性
      • 14.6.1 手动刷新配置属性
      • 14.6.2 自动刷新配置属性
    • 14.7 总结
  • 第 15 章 处理失败和时延
    • 15.1 了解断路器
    • 15.2 定义断路器
      • 15.2.1 缓解时延
      • 15.2.2 管理断路器阈值
    • 15.3 管理失败事件
      • 15.3.1 介绍 Hystrix 面板
      • 15.3.2 了解 Hystrix 线程池
    • 15.4 聚合多个 Hystrix 流
    • 15.5 总结
  • 第五部分 部署Spring
  • 第 16 章 使用 SpringBoot Actuator
    • 16.1 介绍 Actuator
      • 16.1.1 配置 Actuator 基本路径
      • 16.1.2 启用和禁用 Actuator 端点
    • 16.2 使用 Actuator 端点
      • 16.2.1 获取重要的应用程序信息
      • 16.2.2 查看配置详细信息
      • 16.2.3 查看应用程序活动
      • 16.2.4 利用运行时指标
    • 16.3 自定义 Actuator
      • 16.3.1 向 /info 端点提供信息
      • 16.3.2 自定义健康指标
      • 16.3.3 注册自定义指标
      • 16.3.4 创建自定义端点
    • 16.4 保护 Actuator
    • 16.5 总结
  • 第 17 章 管理 Spring
    • 17.1 使用 SpringBoot Admin
      • 17.1.1 创建 Admin 服务端
      • 17.1.2 注册 Admin 客户端
    • 17.2 深入 Admin 服务端
      • 17.2.1 查看普通应用程序运行状况和信息
      • 17.2.2 观察关键指标
      • 17.2.3 检查环境属性
      • 17.2.4 查看并设置 log 级别
      • 17.2.5 监控线程
      • 17.2.6 追踪 HTTP 请求
    • 17.3 保护 Admin 服务端
      • 17.3.1 在 Admin 服务端中启用登录
      • 17.3.2 使用 Actuator 进行认证
    • 17.4 总结
  • 第 18 章 使用 JMX 监控 Spring
    • 18.1 使用 Actuator MBean
    • 18.2 创建自己的 MBean
    • 18.3 发送通知
    • 18.4 总结
  • 第 19 章 部署 Spring
    • 19.1 权衡部署选项
    • 19.2 构建并部署 WAR 文件
    • 19.3 将 JAR 文件推送到 Cloud Foundry
    • 19.4 在 Docker 容器中运行 SpringBoot
    • 19.5 终章
    • 19.6 总结
由 GitBook 提供支持
在本页

这有帮助吗?

  1. 第 3 章 处理数据
  2. 3.1 使用 JDBC 读写数据

3.1.4 插入数据

到此,已经了解了如何使用 JdbcTemplate 向数据库写入数据。JdbcIngredientRepository 中的 save() 方法使用 JdbcTemplate 的 update() 方法将 Ingredient 对象保存到数据库中。

虽然这是第一个很好的例子,但是它可能有点太简单了。保存数据可能比 JdbcIngredientRepository 所需要的更复杂。使用 JdbcTemplate 保存数据的两种方法包括:

  • 直接使用 update() 方法

  • 使用 SimpleJdbcInsert 包装类

让我们首先看看,当持久话需求比保存一个 Ingredient 所需要的更复杂时,如何使用 update() 方法。

使用 JdbcTemplate 保存数据

目前,Taco 和 Order 存储库需要做的惟一一件事是保存它们各自的对象。为了保存 Taco 对象,TacoRepository 声明了一个 save() 方法,如下所示:

package tacos.data;
​
import tacos.Taco;
​
public interface TacoRepository {
    Taco save(Taco design);
}

类似地,OrderRepository 也声明了一个 save() 方法:

package tacos.data;
​
import tacos.Order;
​
public interface OrderRepository {
    Order save(Order order);
}

看起来很简单,对吧?没那么快。保存一个 Taco 设计需要将与该 Taco 关联的 Ingredient 保存到 Taco_Ingredient 表中。同样,保存 Order 也需要将与 Order 关联的 Taco 保存到 Taco_Order_Tacos 表中。这使得保存 Taco 和 Order 比 保存 Ingredient 更有挑战性。

要实现 TacoRepository,需要一个 save() 方法,该方法首先保存基本的 Taco 设计细节(例如,名称和创建时间),然后为 Taco 对象中的每个 Ingredient 在 Taco_Ingredients 中插入一行。下面的程序清单显示了完整的 JdbcTacoRepository 类。

程序清单 3.10 使用 JdbcTemplate 实现 TacoRepository
package tacos.data;
​
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;
​
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
​
import tacos.Ingredient;
import tacos.Taco;
​
@Repository
public class JdbcTacoRepository implements TacoRepository {

    private JdbcTemplate jdbc;

    public JdbcTacoRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    @Override
    public Taco save(Taco taco) {
        long tacoId = saveTacoInfo(taco);
        taco.setId(tacoId);
        for (Ingredient ingredient : taco.getIngredients()) {
            saveIngredientToTaco(ingredient, tacoId);
        }
        return taco;
    }

    private long saveTacoInfo(Taco taco) {
        taco.setCreatedAt(new Date());
        PreparedStatementCreator psc = new PreparedStatementCreatorFactory(
            "insert into Taco (name, createdAt) values (?, ?)",
            Types.VARCHAR, Types.TIMESTAMP
        ).newPreparedStatementCreator(
            Arrays.asList(
                taco.getName(),
                new Timestamp(taco.getCreatedAt().getTime())));

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbc.update(psc, keyHolder);
        return keyHolder.getKey().longValue();
    }

    private void saveIngredientToTaco(Ingredient ingredient, long tacoId) {
        jdbc.update(
            "insert into Taco_Ingredients (taco, ingredient) " +"values (?, ?)",
            tacoId, ingredient.getId());
    }
}

save() 方法首先调用私有的 saveTacoInfo() 方法,然后使用该方法返回的 Taco id 调用 saveIngredientToTaco(),它保存每个成分。关键在于 saveTacoInfo() 的细节。

在 Taco 中插入一行时,需要知道数据库生成的 id,以便在每个 Ingredient 中引用它。保存 Ingredient 数据时使用的 update() 方法不能获得生成的 id,因此这里需要一个不同的 update() 方法。

需要的 update() 方法接受 PreparedStatementCreator 和 KeyHolder。KeyHolder 将提供生成的 Taco id,但是为了使用它,还必须创建一个 PreparedStatementCreator。

如程序清单 3.10 所示,创建 PreparedStatementCreator 非常重要。首先创建一个 PreparedStatementCreatorFactory,为它提供想要执行的 SQL,以及每个查询参数的类型。然后在该工厂上调用 newPreparedStatementCreator(),在查询参数中传递所需的值以生成 PreparedStatementCreator。

通过使用 PreparedStatementCreator,可以调用 update(),传入 PreparedStatementCreator 和 KeyHolder(在本例中是 GeneratedKeyHolder 实例)。update() 完成后,可以通过返回 keyHolder.getKey().longValue() 来返回 Taco id。

回到 save() 方法,循环遍历 Taco 中的每个成分,调用 saveIngredientToTaco() 方法。saveIngredientToTaco() 方法使用更简单的 update() 形式来保存对 Taco_Ingredient 表引用。

TacoRepository 剩下所要做的就是将它注入到 DesignTacoController 中,并在保存 Taco 时使用它。下面的程序清单显示了注入存储库所需的改变。

程序清单 3.11 注入并使用 TacoRepository
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
    private final IngredientRepository ingredientRepo;
    private TacoRepository designRepo;

    @Autowired
    public DesignTacoController(
        IngredientRepository ingredientRepo,
        TacoRepository designRepo) {
        this.ingredientRepo = ingredientRepo;
        this.designRepo = designRepo;
    }

    ...
​
}

构造函数包含一个 IngredientRepository 和一个TacoRepository。它将这两个变量都赋值给实例变量,以便它们可以在 showDesignForm() 和 processDesign() 方法中使用。

说到 processDesign() 方法,它的更改比 showDesignForm() 所做的更改要广泛一些。下一个程序清单显示了新的 processDesign() 方法。

程序清单 3.12 保存 Taco 设计并链接到 Order
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {

    @ModelAttribute(name = "order")
    public Order order() {
        return new Order();
    }

    @ModelAttribute(name = "taco")
    public Taco taco() {
        return new Taco();
    }

    @PostMapping
    public String processDesign(
        @Valid Taco design, Errors errors,
        @ModelAttribute Order order) {

        if (errors.hasErrors()) {
            return "design";
        }

        Taco saved = designRepo.save(design);
        order.addDesign(saved);

        return "redirect:/orders/current";
    }
    ...
}

关于程序清单 3.12 中的代码,首先注意到的是 DesignTacoController 现在使用 @SessionAttributes(“order”) 进行了注解,并且在 order() 方法上有一个新的注解 @ModelAttribute。与 taco() 方法一样,order() 方法上的 @ModelAttribute 注解确保在模型中能够创建 Order 对象。但是与 session 中的 Taco 对象不同,这里需要在多个请求间显示订单,因此可以创建多个 Taco 并将它们添加到订单中。类级别的 @SessionAttributes 注解指定了任何模型对象,比如应该保存在会话中的 order 属性,并且可以跨多个请求使用。

taco 设计的实际处理发生在 processDesign() 方法中,除了 Taco 和 Errors 对象外,该方法现在还接受 Order 对象作为参数。Order 参数使用 @ModelAttribute 进行注解,以指示其值应该来自模型,而 Spring MVC 不应该试图给它绑定请求参数。

在检查验证错误之后,processDesign() 使用注入的 TacoRepository 来保存 Taco。然后,它将 Taco 对象添加到保存于 session 中 Order 对象中。

实际上,Order 对象仍然保留在 session 中,直到用户完成并提交 Order 表单才会保存到数据库中。此时,OrderController 需要调用 OrderRepository 的实现来保存订单。我们来写一下这个实现。

使用 SimpleJdbcInsert 插入数据

保存一个 taco 不仅要将 taco 的名称和创建时间保存到 Taco 表中,还要将与 taco 相关的配料的引用保存到 Taco_Ingredient 表中。对于这个操作还需要知道 Taco 的 id,这是使用 KeyHolder 和 PreparedStatementCreator 来获得的。

在保存订单方面,也存在类似的情况。不仅必须将订单数据保存到 Taco_Order 表中,还必须引用 Taco_Order_Tacos 表中的每个 taco。但是不是使用繁琐的 PreparedStatementCreator, 而是使用SimpleJdbcInsert, SimpleJdbcInsert 是一个包装了 JdbcTemplate 的对象,它让向表插入数据的操作变得更容易。

首先创建一个 JdbcOrderRepository,它是 OrderRepository 的一个实现。但是在编写 save() 方法实现之前,让我们先关注构造函数,在构造函数中,将创建两个 SimpleJdbcInsert 实例,用于将值插入 Taco_Order 和 Taco_Order_Tacos 表中。下面的程序清单显示了 JdbcOrderRepository(没有 save() 方法)。

程序清单 3.13 从 JdbcTemplate 创建一个 SimpleJdbcTemplate
package tacos.data;
​
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.databind.ObjectMapper;
​
import tacos.Taco;
import tacos.Order;
​
@Repository
public class JdbcOrderRepository implements OrderRepository {

    private SimpleJdbcInsert orderInserter;
    private SimpleJdbcInsert orderTacoInserter;
    private ObjectMapper objectMapper;

    @Autowired
    public JdbcOrderRepository(JdbcTemplate jdbc) {
        this.orderInserter = new SimpleJdbcInsert(jdbc)
            .withTableName("Taco_Order")
            .usingGeneratedKeyColumns("id");

        this.orderTacoInserter = new SimpleJdbcInsert(jdbc)
            .withTableName("Taco_Order_Tacos");

        this.objectMapper = new ObjectMapper();
    }
    ...
}

与 JdbcTacoRepository 一样,JdbcOrderRepository 也通过其构造函数注入了 JdbcTemplate。但是,构造函数并没有将 JdbcTemplate 直接分配给一个实例变量,而是使用它来构造两个 SimpleJdbcInsert 实例。

第一个实例被分配给 orderInserter 实例变量,它被配置为使用 Taco_Order 表,并假定 id 属性将由数据库提供或生成。分配给 orderTacoInserter 的第二个实例被配置为使用 Taco_Order_Tacos 表,但是没有声明如何在该表中生成任何 id。

构造函数还创建 ObjectMapper 实例,并将其分配给实例变量。尽管 Jackson 用于 JSON 处理,但稍后将看到如何重新使用它来帮助保存订单及其关联的 tacos。

现在让我们看看 save() 方法如何使用 SimpleJdbcInsert 实例。下一个程序清单显示了 save() 方法,以及几个用于实际工作的 save() 委托的私有方法。

程序清单 3.14 使用 SimpleJdbcInsert 插入数据
@Override
public Order save(Order order) {
    order.setPlacedAt(new Date());
    long orderId = saveOrderDetails(order);
    order.setId(orderId);

    List<Taco> tacos = order.getTacos();
    for (Taco taco : tacos) {
        saveTacoToOrder(taco, orderId);
    }

    return order;
}
​
private long saveOrderDetails(Order order) {
    @SuppressWarnings("unchecked")
    Map<String, Object> values = objectMapper.convertValue(order, Map.class);
    values.put("placedAt", order.getPlacedAt());
​
    long orderId = orderInserter.executeAndReturnKey(values).longValue();

    return orderId;
}
​
private void saveTacoToOrder(Taco taco, long orderId) {
    Map<String, Object> values = new HashMap<>();
    values.put("tacoOrder", orderId);
    values.put("taco", taco.getId());

    orderTacoInserter.execute(values);
}

save() 方法实际上并不保存任何东西。它定义了保存订单及其关联 Taco 对象的流,并将持久性工作委托给 saveOrderDetails() 和 saveTacoToOrder()。

SimpleJdbcInsert 有两个执行插入的有用方法:execute() 和 executeAndReturnKey()。两者都接受 Map<String, Object>,其中 Map 键对应于数据插入的表中的列名,映射的值被插入到这些列中。

通过将 Order 中的值复制到 Map 的条目中,很容易创建这样的 Map。但是 Order 有几个属性,这些属性和它们要进入的列有相同的名字。因此,在 saveOrderDetails() 中,我决定使用 Jackson 的 ObjectMapper 及其 convertValue() 方法将 Order 转换为 Map。这是必要的,否则 ObjectMapper 会将 Date 属性转换为 long,这与 Taco_Order 表中的 placedAt 字段不兼容。

随着 Map 中填充完成订单数据,我们可以在 orderInserter 上调用 executeAndReturnKey() 方法了。这会将订单信息保存到 Taco_Order 表中,并将数据库生成的 id 作为一个 Number 对象返回,调用 longValue() 方法将其转换为从方法返回的 long 值。

saveTacoToOrder() 方法要简单得多。不是使用 ObjectMapper 将对象转换为 Map,而是创建 Map 并设置适当的值。同样,映射键对应于表中的列名。对 orderTacoInserter 的 execute() 方法的简单调用就能执行插入操作。

现在可以将 OrderRepository 注入到 OrderController 中并开始使用它。下面的程序清单显示了完整的 OrderController,包括因使用注入的 OrderRepository 而做的更改。

程序清单 3.15 在 OrderController 中使用 OrderRepository
package tacos.web;
​
import javax.validation.Valid;
​
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
​
import tacos.Order;
import tacos.data.OrderRepository;
​
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {

    private OrderRepository orderRepo;

    public OrderController(OrderRepository orderRepo) {
        this.orderRepo = orderRepo;
    }

    @GetMapping("/current")
    public String orderForm() {
        return "orderForm";
    }

    @PostMapping
    public String processOrder(@Valid Order order, Errors errors,
                               SessionStatus sessionStatus) {
        if (errors.hasErrors()) {
            return "orderForm";
        }

        orderRepo.save(order);
        sessionStatus.setComplete();
        return "redirect:/";
    }
}

除了将 OrderRepository 注入控制器之外,OrderController 中惟一重要的更改是 processOrder() 方法。在这里,表单中提交的 Order 对象(恰好也是在 session 中维护的 Order 对象)通过注入的 OrderRepository 上的 save() 方法保存。

一旦订单被保存,就不再需要它存在于 session 中了。事实上,如果不清除它,订单将保持在 session 中,包括其关联的 tacos,下一个订单将从旧订单中包含的任何 tacos 开始。因此需要 processOrder() 方法请求 SessionStatus 参数并调用其 setComplete() 方法来重置会话。

所有的 JDBC 持久化代码都准备好了。现在,可以启动 Taco Cloud 应用程序并进行测试。你想要多少 tacos 和多少 orders 都可以。

Spring 的 JdbcTemplate 和 SimpleJdbcInsert 使得使用关系数据库比普通 JDBC 简单得多。但是可能会发现 JPA 使它更加简单。让我们回顾一下之前的工作,看看如何使用 Spring 数据使数据持久化更加容易。

上一页3.1.3 定义模式并预加载数据下一页3.2 使用 Spring Data JPA 持久化数据

最后更新于4年前

这有帮助吗?

可能还会发现在数据库中进行挖掘是很有帮助的。因为使用 H2 作为嵌入式数据库,而且 Spring Boot DevTools 已经就位,所以应该能够用浏览器访问 来查看 H2 控制台。虽然需要确保 JDBC URL 字段被设置为 JDBC:h2:mem:testdb,但是默认的凭证应该可以让你进入。登录后,应该能够对 Taco Cloud 模式中的表发起查询。

http://localhost:8080/h2-console