30、使用 SQL 数据库

Spring Framework 为 SQL 数据库提供了广泛的支持。从直接使用 JdbcTemplate 进行 JDBC 访问到完全的对象关系映射(object relational mapping)技术,比如 Hibernate。Spring Data 提供了更多级别的功能,直接从接口创建的 Repository 实现,并使用了约定从方法名生成查询。

30.1、配置数据源

Java 的 javax.sql.DataSource 接口提供了一个使用数据库连接的标准方法。通常,数据源使用 URL 和一些凭据信息来建立数据库连接。

提示

查看 How-to 部分获取更多高级示例,通常您可以完全控制数据库的配置。

30.1.1、内嵌数据库支持

使用内嵌内存数据库来开发应用程序非常方便的。显然,内存数据库不提供持久存储。在应用启动时,您需要填充数据库,并在应用程序结束时丢弃数据。

提示

How-to 部分包含了如何初始化数据库方面的内容。

Spring Boot 可以自动配置内嵌 H2HSQLDerby 数据库。您不需要提供任何连接 URL,只需为您想要使用的内嵌数据库引入特定的构建依赖。

注意

如果您在测试中使用此功能,您可能会注意到,无论使用了多少应用程序上下文,整个测试套件都会重复使用相同的数据库。如果您想确保每个上下文都有一个单独的内嵌数据库,则应该将 spring.datasource.generate-unique-name 设置为 true

以下是 POM 依赖示例:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-jpa</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.hsqldb</groupId>
  7. <artifactId>hsqldb</artifactId>
  8. <scope>runtime</scope>
  9. </dependency>

注意

要自动配置内嵌数据库,您需要一个 spring-jdbc 依赖。在这个例子中,它是通过 spring-boot-starter-data-jpa 引入。

提示

如果出于某些原因,您需要配置内嵌数据库的连接 URL,则应注意确保禁用数据库的自动关闭功能。如果您使用 H2,则应该使用 DB_CLOSE_ON_EXIT=FALSE 来设置。如果您使用 HSQLDB,则确保不使用 shutdown=true。禁用数据库的自动关闭功能允许 Spring Boot 控制数据库何时关闭,从而确保一旦不再需要访问数据库时就触发。

30.1.2、连接生产数据库

生产数据库连接也可以使用使用 DataSource 自动配置。Spring Boot 使用以下算法来选择一个特定的实现:

  • 出于性能和并发性的考虑,我们更喜欢 HikariCP 连接池。如果 HikariCP 可用,我们总是选择它。
  • 否则,如果 Tomcat 池 DataSource 可用,我们将使用它。
  • 如果 HikariCP 和 Tomcat 池数据源不可用,但 Commons DBCP 可用,我们将使用它。

如果您使用了 spring-boot-starter-jdbc 或者 spring-boot-starter-data-jpa starter,您将自动得到 HikariCP 依赖。

注意

您完全可以绕过该算法,并通过 spring.datasource.type 属性指定要使用的连接池。如果您在 Tomcat 容器中运行应用程序,默认提供 tomcat-jdbc,这点尤其重要。

提示

可以手动配置其他连接池。如果您定义了自己的 DataSource bean,则自动配置将不会触发。

数据源配置由 spring.datasource.* 中的外部属性所控制。例如,您可以在 application.properties 中声明以下部分:

  1. spring.datasource.url=jdbc:mysql://localhost/test
  2. spring.datasource.username=dbuser
  3. spring.datasource.password=dbpass
  4. spring.datasource.driver-class-name=com.mysql.jdbc.Driver

注意

您至少应该使用 spring.datasource.url 属性来指定 URL,否则 Spring Boot 将尝试自动配置内嵌数据库。

提示

通常您不需要指定 driver-class-name,因为 Spring boot 可以从 url 推导出大多数数据库。

注意

对于要创建的池 DataSource,我们需要能够验证有效的 Driver 类是否可用,因此我们在使用之前进行检查。例如,如果您设置了 spring.datasource.driver-class-name=com.mysql.jdbc.Driver,那么该类必须可加载。

有关更多支持选项,请参阅 DataSourceProperties。这些都是标准选项,与实际的实现无关。还可以使用各自的前缀(spring.datasource.hikari.*spring.datasource.tomcat.*spring.datasource.dbcp2.*)微调实现特定的设置。请参考您现在使用的连接池实现的文档来获取更多信息。

例如,如果你使用 Tomcat 连接池,则可以自定义许多其他设置,如下:

  1. # Number of ms to wait before throwing an exception if no connection is available.
  2. spring.datasource.tomcat.max-wait=10000
  3. # Maximum number of active connections that can be allocated from this pool at the same time.
  4. spring.datasource.tomcat.max-active=50
  5. # Validate the connection before borrowing it from the pool.
  6. spring.datasource.tomcat.test-on-borrow=true

30.1.3、连接 JNDI 数据源

如果要将 Spring Boot 应用程序部署到应用服务器(Application Server)上,您可能想使用应用服务器的内置功能和 JNDI 访问方式来配置和管理数据源。

spring.datasource.jndi-name 属性可作为 spring.datasource.urlspring.datasource.usernamespring.datasource.password 属性的替代方法,用于从特定的 JNDI 位置访问 DataSource。例如,application.properties 中的以下部分展示了如何访问 JBoss AS 定义的 DataSource

  1. spring.datasource.jndi-name=java:jboss/datasources/customers

30.2、使用 JdbcTemplate

Spring 的 JdbcTemplateNamedParameterJdbcTemplate 类是自动配置的,您可以使用 @Autowire 将它们直接注入您的 bean 中:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.jdbc.core.JdbcTemplate;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class MyBean {
  6. private final JdbcTemplate jdbcTemplate;
  7. @Autowired
  8. public MyBean(JdbcTemplate jdbcTemplate) {
  9. this.jdbcTemplate = jdbcTemplate;
  10. }
  11. // ...
  12. }

您可以使用 spring.jdbc.template.* 属性来自定义一些 template 的属性,如下:

  1. spring.jdbc.template.max-rows=500

注意

NamedParameterJdbcTemplate 在底层重用了相同的 JdbcTemplate 实例。如果定义了多个 JdbcTemplate 且没有声明 primary 主候选,则不会自动配置 NamedParameterJdbcTemplate

30.3、JPA 与 Spring Data JPA

Java Persistence API(Java 持久化 API)是一项标准技术,可让您将对象映射到关系数据库。spring-boot-starter-data-jpa POM 提供了一个快速起步的方法。它提供了以下关键依赖:

  • Hibernate  ——  最受欢迎的 JPA 实现之一。
  • Spring Data JPA ——  可以轻松地实现基于 JPA 的资源库。
  • Spring ORM  ——  Spring Framework 的核心 ORM 支持

提示

我们不会在这里介绍太多关于 JPA 或者 Spring Data 的相关内容。您可以在 spring.io 上查看使用 JPA 访问数据,获取阅读 Spring Data JPAHibernate 的参考文档。

30.3.1、实体类

通常,JPA Entity(实体)类是在 persistence.xml 文件中指定的。使用了 Spring Boot,该文件将不是必需的,可以使用 Entity Scanning(实体扫描)来代替。默认情况下,将搜索主配置类(使用了 @EnableAutoConfiguration@SpringBootApplication 注解)下面的所有包。

任何用了 @Entity@Embeddable 或者 @MappedSuperclass 注解的类将被考虑。一个典型的实体类如下:

  1. package com.example.myapp.domain;
  2. import java.io.Serializable;
  3. import javax.persistence.*;
  4. @Entity
  5. public class City implements Serializable {
  6. @Id
  7. @GeneratedValue
  8. private Long id;
  9. @Column(nullable = false)
  10. private String name;
  11. @Column(nullable = false)
  12. private String state;
  13. // ... additional members, often include @OneToMany mappings
  14. protected City() {
  15. // no-args constructor required by JPA spec
  16. // this one is protected since it shouldn't be used directly
  17. }
  18. public City(String name, String state) {
  19. this.name = name;
  20. this.state = state;
  21. }
  22. public String getName() {
  23. return this.name;
  24. }
  25. public String getState() {
  26. return this.state;
  27. }
  28. // ... etc
  29. }

提示

您可以使用 @EntityScan 注解自定义实体类的扫描位置。请参见84.4、从 Spring configuration 配置中分离 @Entity 定义章节。

30.3.2、Spring Data JPA 资源库

Spring Data JPA 资源库(repository)是接口,您可以定义用于访问数据。JAP 查询是根据您的方法名自动创建。例如,CityRepository 接口可以声明 findAllByState(String state) 方法来查找指定状态下的所有城市。

对于更加复杂的查询,您可以使用 Spring Data 的 Query 注解

Spring Data 资源库通常继承自 Repository 或者 CrudRepository 接口。如果您使用了自动配置,则将从包含主配置类(使用了 @EnableAutoConfiguration@SpringBootApplication 注解)的包中搜索资源库:

以下是一个典型的 Spring Data 资源库接口定义:

  1. package com.example.myapp.domain;
  2. import org.springframework.data.domain.*;
  3. import org.springframework.data.repository.*;
  4. public interface CityRepository extends Repository<City, Long> {
  5. Page<City> findAll(Pageable pageable);
  6. City findByNameAndStateAllIgnoringCase(String name, String state);
  7. }

Spring Data JPA 资源库支持三种不同的引导模式:default、deferred 和 lazy。要启用延迟或懒惰引导,请将 spring.data.jpa.repositories.bootstrap-mode 分别设置为 deferredlazy。使用延迟或延迟引导时,自动配置的 EntityManagerFactoryBuilder 将使用上下文的异步任务执行器(如果有)作为引导程序执行器。

提示

我们几乎没有接触到 Spring Data JPA 的表面内容。有关详细信息,请查阅 Spring Data JPA 参考文档

30.3.3、创建和删除 JPA 数据库

默认情况下,当您使用了内嵌数据库(H2、HSQL 或 Derby)时才会自动创建 JPA 数据库。您可以使用 spring.jpa.* 属性显式配置 JPA 设置。例如,要创建和删除表,您可以将以下内容添加到 application.properties 中:

  1. spring.jpa.hibernate.ddl-auto=create-drop

注意

关于上述功能,Hibernate 自己的内部属性名称(如果您记住更好)为 hibernate.hbm2ddl.auto。您可以使用 spring.jpa.properties.*(在添加到实体管理器之前,该前缀将被删除)来将 Hibernate 原生属性一同设置:

  1. spring.jpa.properties.hibernate.globally_quoted_identifiers=true

上面示例中将 true 值设置给 hibernate.globally_quoted_identifiers 属性,该属性将传给 Hibernate 实体管理器。

默认情况下,DDL 执行(或验证)将延迟到 ApplicationContext 启动后。还有一个 spring.jpa.generate-ddl 标志,如果 Hibernate 自动配置是激活的,那么它将不会被使用,因为 ddl-auto 设置更细粒度。

30.3.4、在视图中打开 EntityManager

如果您正在运行 web 应用程序,Spring Boot 将默认注册 OpenEntityManagerInViewInterceptor 用于在视图中打开 EntityManager 模式,即运允许在 web 视图中延迟加载。如果您不想开启这个行为,则应在 application.properties 中将 spring.jpa.open-in-view 设置为 false

30.4、Spring Data JDBC

Spring Data 包含了对 JDBC 资源库的支持,并将自动为 CrudRepository 上的方法生成 SQL。对于更高级的查询,它提供了 @Query 注解。

当 classpath 下存在必要的依赖时,Spring Boot 将自动配置 Spring Data 的 JDBC 资源库。可以通过添加单个 spring-boot-starter-data-jdbc 依赖引入到项目中。如有必要,可通过在应用程序中添加 @EnableJdbcRepositories 注解或 JdbcConfiguration 子类来控制 Spring Data JDBC 的配置。

提示

有关 Spring Data JDBC 的完整详细信息,请参阅参考文档

30.5、使用 H2 的 Web 控制台

H2 数据库提供了一个基于浏览器的控制台,Spring Boot 可以为您自动配置。当满足以下条件时,控制台将自动配置:

提示

如果您不使用 Spring Boot 的开发者工具,但仍希望使用 H2 的控制台,则可以通过将 spring.h2.console.enabled 属性设置为 true 来实现。

注意

H2 控制台仅用于开发期间,因此应注意确保 spring.h2.console.enabled 在生产环境中没有设置为 true

30.5.1、更改 H2 控制台的路径

默认情况下,控制台的路径为 /h2-console。你可以使用 spring.h2.console.path 属性来自定义控制台的路径。

30.6、使用 jOOQ

Java 面向对象查询(Java Object Oriented Querying,jOOQ)是一款广受欢迎的产品,出自 Data Geekery,它可以通过数据库生成 Java 代码,并允许您使用流式 API 来构建类型安全的 SQL 查询。商业版和开源版都可以与 Spring Boot 一起使用。

30.6.1、代码生成

要使用 jOOQ 的类型安全查询,您需要从数据库模式生成 Java 类。您可以按照 jOOQ 用户手册中的说明进行操作。如果您使用了 jooq-codegen-maven 插件,并且还使用了 spring-boot-starter-parent 父 POM,则可以安全地省略掉插件的 <version> 标签。您还可以使用 Spring Boot 定义的版本变量(例如 h2.version)来声明插件的数据库依赖。以下是一个示例:

  1. <plugin>
  2. <groupId>org.jooq</groupId>
  3. <artifactId>jooq-codegen-maven</artifactId>
  4. <executions>
  5. ...
  6. </executions>
  7. <dependencies>
  8. <dependency>
  9. <groupId>com.h2database</groupId>
  10. <artifactId>h2</artifactId>
  11. <version>${h2.version}</version>
  12. </dependency>
  13. </dependencies>
  14. <configuration>
  15. <jdbc>
  16. <driver>org.h2.Driver</driver>
  17. <url>jdbc:h2:~/yourdatabase</url>
  18. </jdbc>
  19. <generator>
  20. ...
  21. </generator>
  22. </configuration>
  23. </plugin>

30.6.2、使用 DSLContext

jOOQ 提供的流式 API 是通过 org.jooq.DSLContext 接口初始化的。Spring Boot 将自动配置一个 DSLContext 作为 Spring Bean,并且将其连接到应用程序的 DataSource要使用 DSLContext,您只需要 @Autowire 它:

  1. @Component
  2. public class JooqExample implements CommandLineRunner {
  3. private final DSLContext create;
  4. @Autowired
  5. public JooqExample(DSLContext dslContext) {
  6. this.create = dslContext;
  7. }
  8. }

提示

jOOQ 手册建议使用名为 create 的变量来保存 DSLContext

您可以使用 DSLContext 构建查询:

  1. public List<GregorianCalendar> authorsBornAfter1980() {
  2. return this.create.selectFrom(AUTHOR)
  3. .where(AUTHOR.DATE_OF_BIRTH.greaterThan(new GregorianCalendar(1980, 0, 1)))
  4. .fetch(AUTHOR.DATE_OF_BIRTH);
  5. }

30.6.3、jOOQ SQL 方言

除非配置了 spring.jooq.sql-dialect 属性,否则 Spring Boot 会自动判定用于数据源的 SQL 方言。如果 Spring Boot 无法检测到方言,则使用 DEFAULT

注意

Spring Boot 只能自动配置 jOOQ 开源版本支持的方言。

30.6.4、自定义 jOOQ

可通过定义自己的 @Bean 来实现更高级的功能,这些自定义将在创建 jOOQ Configuration 时使用。您可以为以下 jOOQ 类型定义 bean:

  • ConnectionProvider
  • ExecutorProvider
  • TransactionProvider
  • RecordMapperProvider
  • RecordUnmapperProvider
  • RecordListenerProvider
  • ExecuteListenerProvider
  • VisitListenerProvider
  • TransactionListenerProvider

如果要完全控制 jOOQ 配置,您可以创建自己的 org.jooq.Configuration @Bean