Spring Start Here 读书笔记:第12章 Using data sources in Spring apps

Spring常用数据库来持久化数据。

12.1 什么是数据源

数据源是一个管理与数据库管理系统 (DBMS) 连接的组件。它使用 JDBC 驱动程序获取其管理的连接。数据源旨在通过允许其逻辑重用与 DBMS 的连接,并仅在需要时请求新连接来提高应用的性能。数据源还会确保在释放连接时关闭它们。

如果没有一个对象承担数据源的责任,应用就需要为每次数据操作请求一个新的连接。由于每次操作都需要通过网络通信来建立新的连接,这会显著降低应用速度并导致性能问题。数据源可以确保应用仅在真正需要时才请求新的连接,从而提升应用的性能。

在 Java 应用中,该语言连接关系数据库的功能被称为 Java 数据库连接 (JDBC)。JDBC 提供了一种连接到 DBMS 以使用数据库的方法。JDK仅提供应用使用关系数据库所需对象的抽象, 并不提供针对特定技术(例如 MySQL、Postgres 或 Oracle)的具体实现。您需要添加一个名为 JDBC 驱动程序的运行时依赖项,由具体的数据库技术供应商提供JDBC 驱动程序。JDBC 驱动程序既不是来自 JDK,也不是来自 Spring 等框架。

// url指定了数据库类型和具体的数据库
// "jdbc:mysql://localhost:3306/mydatabase"
// "jdbc:postgresql://localhost:5432/mydatabase"
// "jdbc:oracle:thin:@//localhost:1521/orclpdb"
Connection con = DriverManager.getConnection(url, username, password);

上述的方法每次都获取一个新的连接,更好的方式是使用数据源来管理连接,从而提升性能,并避免认证,创建和关闭连接等额外操作。

对于 Java 应用,目前最常用的是开源的 HikariCP(Hikari 连接池)数据源。Spring Boot 的约定配置也将 HikariCP 视为默认数据源实现。

12.2 使用 JdbcTemplate 处理持久化数据

JdbcTemplate 是 Spring 提供的使用关系数据库的最简单的工具,
但它对于小型应用来说是一个绝佳的选择,因为它不会强制你使用任何其他特定的持久层框架。当你不希望你的应用有任何其他依赖项时,JdbcTemplate 是实现持久层的最佳 Spring 选择。它也是学习如何实现 Spring 应用持久层的绝佳入门方法。

相关知识,JDBC 部分推荐 Jeanne Boyarsky 和 Scott Selikoff 合著的《OCP Oracle Certified Professional Java SE 11 Developer Complete Study Guide》(Sybex,2020 年)的第 21 章。SQL推荐 Alan Beaulieu 合著的《Learning SQL》第三版(O’Reilly Media,2020 年)。

参考示例sq-ch12-ex1。我们将开发一个后端服务,它会暴露两个端点。客户端调用一个端点在购买表中添加一条新记录,并调用另一个端点获取购买表中的所有记录。

首先用Spring Initializr初始化项目:
在这里插入图片描述
我们需要的是Spring核心的JDBC,即JDBC API。而非Spring Session for JDBC和Spring Data JDBC。
在这里插入图片描述

数据库使用的是H2,嵌入式内存SQL数据库,Java写的。但对于 H2,您无需单独添加它,因为它已包含在您在 pom.xml 文件中添加的 H2 数据库依赖项中。

schema.sql放置在目录resources下,会自动创建表。

CREATE TABLE IF NOT EXISTS purchase (
    id INT AUTO_INCREMENT PRIMARY KEY,
    product varchar(50) NOT NULL,
    price double NOT NULL
);

model类为Purchase,注意金额属性是用BigDecimal,而非double,因为其会丢失精度。

public class Purchase {

    private int id;
    private String product;
    private BigDecimal price;
    ...
}

存储库(repository)是负责处理数据库的类。本例为PurchaseRepository。其中包含JdbcTemplate以与数据库交互。当 Spring Boot 检测到你在 pom.xml 中添加了 H2 依赖项时,它会自动配置一个数据源和一个 JdbcTemplate 实例。在本例中,我们将直接使用它们。

@Repository
public class PurchaseRepository {

    private final JdbcTemplate jdbc;

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

    public void storePurchase(Purchase purchase) {
    	String sql = "INSERT INTO purchase(product, price) VALUES (?, ?)";
        jdbc.update(sql, purchase.getProduct(), purchase.getPrice());
    }

    public List<Purchase> findAllPurchases() {
        String sql = "SELECT * FROM purchase";

        RowMapper<Purchase> purchaseRowMapper = (r, i) -> {
            Purchase rowObject = new Purchase();
            rowObject.setId(r.getInt("id"));
            rowObject.setProduct(r.getString("product"));
            rowObject.setPrice(r.getBigDecimal("price"));
            return rowObject;
        };

        return jdbc.query(sql, purchaseRowMapper);
    }
}

注意,原代码中的INSERT语句有误,我已经改了(详见String sql)。原语句如下:

"INSERT INTO purchase VALUES (NULL, ?, ?)";

RowMapper这段 Java 代码定义了一个实现 RowMapper<Purchase>接口的 lambda 表达式,该接口通常用于 Spring 的 JdbcTemplate 中,将 SQL 结果集中的一行映射到 Java 对象 - 在本例中为 Purchase 对象。整体返回的是Purchase对象的列表。

最后看一下控制类PurchaseController,映射了两个REST端点:

@RestController
@RequestMapping("/purchase")
public class PurchaseController {

    private final PurchaseRepository purchaseRepository;

    public PurchaseController(PurchaseRepository purchaseRepository) {
        this.purchaseRepository = purchaseRepository;
    }

    @PostMapping
    public void storePurchase(@RequestBody Purchase purchase) {
        purchaseRepository.storePurchase(purchase);
    }

    @GetMapping
    public List<Purchase> findPurchases() {
        return purchaseRepository.findAllPurchases();
    }
}

运行如下:

$ curl 'http://localhost:8080/purchase'
[]

$ curl -XPOST 'http://localhost:8080/purchase' -H 'Content-Type: application/json' -d '{
"product" : "Spring Security in Action",
"price" : 25.2
}'

$ curl 'http://localhost:8080/purchase'
[{"id":1,"product":"Spring Security in Action","price":25.2}]

12.3 自定义数据源配置

本例中使用自定义数据源PostgreSQL,而非H2数据库。书中使用的是MySQL,但由于我有一个现成的PG,因此就直接用了。

12.3.1 在应用程序属性文件中定义数据源

在这里插入图片描述
去掉H2的依赖,增加了PostgreSQL的依赖:

<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
</dependency>

schema.sql需改为符合PostgreSQL语法:

CREATE TABLE IF NOT EXISTS purchase (
    id SERIAL PRIMARY KEY,
    product varchar(50) NOT NULL,
    price double PRECISION NOT NULL
);

application.properties需改为如下:

spring.application.name=sq-ch12-ex1
spring.datasource.url=jdbc:postgresql://localhost:5432/sampledb
spring.datasource.username=hr
spring.datasource.password=Welcome1
spring.sql.init.mode=always

spring.sql.init.mode=always始终在启动时运行 schema.sql 和 data.sql,这是Spring Boot 2.5版本后的新语法。原代码中的spring.datasource.initialization-mode=always是错的。

也就改了以上两个文件,运行结果同上例。

12.3.2 使用自定义 DataSource bean

有时,您不能依赖 Spring Boot 来创建 DataSource Bean。在这种情况下,您需要自己定义 Bean。场景如下:

  • 您需要根据只能在运行时获取的条件使用特定的 DataSource 实现。
  • 您的应用连接到多个数据库,因此您必须创建多个数据源,并使用限定符区分它们。
  • 您必须在应用仅在运行时才具有的某些条件下配置 DataSource 对象的特定参数。例如,根据启动应用的环境,您希望在连接池中拥有更多或更少的连接,以优化性能。
  • 您的应用使用 Spring 框架,而不是 Spring Boot。

参见示例sqch12-ex3。先在配置类中定义一个DataSource bean:

@Configuration
public class ProjectConfig {

  @Value("${custom.datasource.url}")
  private String datasourceUrl;

  @Value("${custom.datasource.username}")
  private String datasourceUsername;

  @Value("${custom.datasource.password}")
  private String datasourcePassword;

  @Bean
  public DataSource dataSource() {
    HikariDataSource dataSource = new HikariDataSource();
    dataSource.setJdbcUrl(datasourceUrl);
    dataSource.setUsername(datasourceUsername);
    dataSource.setPassword(datasourcePassword);
    dataSource.setConnectionTimeout(1000);
    return dataSource;
  }
}

注意@Value中的属性,都是以custom开头的,这并不是Spring的属性,因此application.properties修改如下:

custom.datasource.url=jdbc:postgresql://localhost:5432/sampledb
custom.datasource.username=hr
custom.datasource.password=Welcome1
spring.sql.init.mode=always

但这里有个问题,purchase表必须预先建好,因为spring.sql.init.mode=always失效了。这倒是正常的行为,如果一定希望表自动建立,可以在配置类ProjectConfig中添加以下代码:

        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(new ClassPathResource("schema.sql"));
        initializer.setDatabasePopulator(populator);

总结

  • 对于 Java 应用程序,Java 开发工具包 (JDK) 提供了应用程序连接到关系数据库所需对象的抽象。应用程序始终需要添加一个运行时依赖项来提供这些抽象的实现。我们将此依赖项称为 JDBC 驱动程序。
  • 数据源是管理与数据库服务器连接的对象。如果没有数据源,应用会过于频繁地请求连接,从而影响其性能。
  • 默认情况下,Spring Boot 会配置一个名为 HikariCP 的数据源实现,它使用连接池来优化应用使用数据库连接的方式。也可使用其他数据源如Tomcat JDBC。
  • JdbcTemplate 是一个 Spring 工具,它简化了使用 JDBC 访问关系数据库的代码。JdbcTemplate 对象依赖于数据源来连接到数据库服务器。
  • 要发送修改表中数据的查询,可以使用 JdbcTemplate 对象的 update() 方法。要发送 SELECT 查询来检索数据,可以使用 JdbcTemplate 的 query() 方法之一。您通常需要使用此类操作来更改或检索持久化数据。
  • 要自定义 Spring Boot 应用使用的数据源,您需要配置一个 java.sql.DataSource 类型的自定义 bean。如果您在 Spring 上下文中声明了此类型的 bean,Spring Boot 将使用它,而不是配置默认 bean。如果您需要自定义 JdbcTemplate 对象,也可以使用相同的方法。我们通常使用 Spring Boot 提供的默认 bean,但特定情况下有时需要自定义配置或实现来进行各种优化。
  • 如果您希望应用连接到多个数据库,可以创建多个数据源对象,每个对象都关联各自的 JdbcTemplate 对象。在这种情况下,您需要使用 @Qualifier 注解来区分应用上下文中相同类型的对象(正如您在第 4 章和第 5 章中学习到的)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值