29. 使用SQL数据库

Spring框架为使用SQL数据库提供了广泛支持,从使用JdbcTemplate直接访问JDBC到完全的‘对象关系映射’技术,比如Hibernate。Spring Data提供了更高级的功能,直接从接口创建Repository实现,并根据约定从方法名生成查询。

29.1. 配置DataSource

Java的javax.sql.DataSource接口提供了一个标准的使用数据库连接的方法。通常,DataSource使用URL和相应的凭证去初始化数据库连接。

29.1.1. 对内嵌数据库的支持

开发应用时使用内存数据库是很方便的。显然,内存数据库不提供持久化存储;你只需要在应用启动时填充数据库,在应用结束前预先清除数据。

Spring Boot可以自动配置的内嵌数据库包括H2, HSQLDerby。你不需要提供任何连接URLs,只需要添加你想使用的内嵌数据库依赖。

示例:典型的POM依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

对于自动配置的内嵌数据库,你需要添加spring-jdbc依赖,在本示例中,spring-boot-starter-data-jpa已包含该依赖了。

无论出于什么原因,你需要配置内嵌数据库的连接URL,一定要确保数据库的自动关闭是禁用的。如果使用H2,你需要设置DB_CLOSE_ON_EXIT=FALSE。如果使用HSQLDB,你需要确保没使用shutdown=true。禁用数据库的自动关闭可以让Spring Boot控制何时关闭数据库,因此在数据库不需要时可以确保关闭只发生一次。

29.1.2. 连接生产环境数据库

生产环境的数据库连接可以通过池化的DataSource进行自动配置,下面是选取特定实现的算法:

  • 出于tomcat数据源连接池的优秀性能和并发,如果可用总会优先使用它。
  • 如果HikariCP可用,我们将使用它。
  • 如果Commons DBCP可用,我们将使用它,但生产环境不推荐。
  • 最后,如果Commons DBCP2可用,我们将使用它。

如果使用spring-boot-starter-jdbcspring-boot-starter-data-jpa 'starters',你会自动添加tomcat-jdbc依赖。

通过指定spring.datasource.type属性,你可以完全抛弃该算法,然后指定数据库连接池。如果你在tomcat容器中运行应用,由于默认提供tomcat-jdbc,这就很重要了。

其他的连接池可以手动配置,如果你定义自己的DataSource bean,自动配置是不会发生的。

DataSource配置被外部的spring.datasource.*属性控制,例如,你可能会在application.properties中声明以下片段:

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
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,然后该class加载出来,否则就会出错。

其他可选配置可以查看DataSourceProperties,有些标准配置是跟实现无关的,对于实现相关的配置可以通过相应前缀进行设置(spring.datasource.tomcat.*spring.datasource.hikari.*spring.datasource.dbcp.*spring.datasource.dbcp2.*),具体参考你使用的连接池文档。

例如,如果正在使用Tomcat连接池,你可以自定义很多其他设置:

# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.tomcat.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=50

# Validate the connection before borrowing it from the pool.
spring.datasource.tomcat.test-on-borrow=true

29.1.3. 连接JNDI数据库

如果正在将Spring Boot应用部署到一个应用服务器,你可能想要用应用服务器内建的特性来配置和管理你的DataSource,并使用JNDI访问它。

spring.datasource.jndi-name属性可用来替代spring.datasource.urlspring.datasource.usernamespring.datasource.password去从一个特定的JNDI路径获取DataSource,比如,以下application.properties中的片段展示了如何获取JBoss AS定义的DataSource

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

29.2. 使用JdbcTemplate

Spring的JdbcTemplateNamedParameterJdbcTemplate类会被自动配置,你可以将它们直接@Autowire到自己的beans:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public MyBean(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    // ...
}

29.3. JPA和Spring Data

Java持久化API是一个允许你将对象映射为关系数据库的标准技术,spring-boot-starter-data-jpa POM提供了一种快速上手的方式,它提供以下关键依赖:

  • Hibernate - 一个非常流行的JPA实现。
  • Spring Data JPA - 让实现基于JPA的repositories更容易。
  • Spring ORMs - Spring框架支持的核心ORM。

我们不想在这涉及太多关于JPA或Spring Data的细节。你可以参考来自spring.io的指南使用JPA获取数据,并阅读Spring Data JPAHibernate的参考文档。

Spring Boot默认使用Hibernate 5.0.x,如果你希望的话也可以使用4.3.x或5.2.x,具体参考Hibernate 4Hibernate 5.2示例。

29.3.1. 实体类

通常,JPA实体类被定义到一个persistence.xml文件,在Spring Boot中,这个文件被'实体扫描'取代。默认情况,Spring Boot会查找主配置类(被@EnableAutoConfiguration@SpringBootApplication注解的类)下的所有包。

任何被@Entity@Embeddable@MappedSuperclass注解的类都将被考虑,一个普通的实体类看起来像这样:

package com.example.myapp.domain;

import java.io.Serializable;
import javax.persistence.*;

@Entity
public class City implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String state;

    // ... additional members, often include @OneToMany mappings

    protected City() {
        // no-args constructor required by JPA spec
        // this one is protected since it shouldn't be used directly
    }

    public City(String name, String state) {
        this.name = name;
        this.country = country;
    }

    public String getName() {
        return this.name;
    }

    public String getState() {
        return this.state;
    }
    // ... etc
}

你可以使用@EntityScan注解自定义实体扫描路径,具体参考Section 74.4, “Separate @Entity definitions from Spring configuration”

29.3.2. Spring Data JPA仓库

Spring Data JPA仓库(repositories)是用来定义访问数据的接口。根据你的方法名,JPA查询会被自动创建,比如,一个CityRepository接口可能声明一个findAllByState(String state)方法,用来查找给定状态的所有城市。

对于比较复杂的查询,你可以使用Spring Data的Query注解你的方法。

Spring Data仓库通常继承自RepositoryCrudRepository接口。如果你使用自动配置,Spring Boot会搜索主配置类(注解@EnableAutoConfiguration@SpringBootApplication的类)所在包下的仓库。

下面是典型的Spring Data仓库:

package com.example.myapp.domain;

import org.springframework.data.domain.*;
import org.springframework.data.repository.*;

public interface CityRepository extends Repository<City, Long> {

    Page<City> findAll(Pageable pageable);

    City findByNameAndCountryAllIgnoringCase(String name, String country);
}

:我们仅仅触及了Spring Data JPA的表面,具体查看它的参考指南

29.3.3. 创建和删除JPA数据库

默认情况下,只有在你使用内嵌数据库(H2, HSQL或Derby)时,JPA数据库才会被自动创建。你可以使用spring.jpa.*属性显式的设置JPA,比如,将以下配置添加到application.properties中可以创建和删除表:

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

Hibernate自己内部对创建,删除表支持的属性是hibernate.hbm2ddl.auto(如果你记得更好)。你可以使用spring.jpa.properties.*(前缀在被添加到实体管理器之前会被去掉)设置Hibernate其他的native属性,比如:spring.jpa.properties.hibernate.globally_quoted_identifiers=true将传递hibernate.globally_quoted_identifiers到Hibernate实体管理器。

通常,DDL执行(或验证)被延迟到ApplicationContext启动后,这可以通过spring.jpa.generate-ddl标签控制,如果Hibernate自动配置被激活,那该标识就不会被使用,因为ddl-auto设置粒度更细。

29.4 使用H2的web控制台

H2数据库提供一个基于浏览器的控制台,Spring Boot可以为你自动配置。如果以下条件满足,则控制台会被自动配置:

如果你没有使用Spring Boot的开发者工具,仍想利用H2的控制台,可以设置spring.h2.console.enabled属性值为true。H2控制台应该只用于开发期间,所以确保生产环境没有设置spring.h2.console.enabled

29.4.1 改变H2控制台路径

H2控制台路径默认为/h2-console,你可以通过设置spring.h2.console.path属性自定义该路径。

29.4.2 保护H2控制台

当添加Spring Security依赖,并且启用基本认证时,Spring Boot自动使用基本认证保护H2控制台。以下属性可用于自定义安全配置:

  • security.user.role
  • security.basic.authorize-mode
  • security.basic.enabled

29.5 使用jOOQ

Java面向对象查询(jOOQ)是Data Geekery的一个明星产品,可以从数据库生成Java代码,让你通过它的流式API构建类型安全的SQL查询。不管是商业版,还是开源版本都能跟Spring Boot一块使用。

29.5.1 代码生成

为了使用jOOQ类型安全的查询,你需要从数据库schema生成Java类,具体可参考jOOQ用户指南。如果正在使用jooq-codegen-maven插件(也使用spring-boot-starter-parent “parent POM”),你可以安全的省略插件的<version>标签,也可以使用Spring Boot定义的版本变量(比如h2.version)来声明插件的数据库依赖,示例如下:

<plugin>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <executions>
        ...
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
        </dependency>
    </dependencies>
    <configuration>
        <jdbc>
            <driver>org.h2.Driver</driver>
            <url>jdbc:h2:~/yourdatabase</url>
        </jdbc>
        <generator>
            ...
        </generator>
    </configuration>
</plugin>

29.5.2 使用DSLContext

jOOQ提供的流式(fluent)API是通过org.jooq.DSLContext接口初始化的,Spring Boot将自动配置一个DSLContext为Spring Bean,并将它跟应用的DataSource连接起来。想要使用DSLContext,只需@Autowire注入它:

@Component
public class JooqExample implements CommandLineRunner {

    private final DSLContext create;

    @Autowired
    public JooqExample(DSLContext dslContext) {
        this.create = dslContext;
    }

}

jOOQ手册倾向于使用一个名为create的变量持有DSLContext,示例中也是这样做的。

然后你就可以使用DSLContext构造查询:

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

29.5.3 自定义jOOQ

通过在application.properties中设置spring.jooq.sql-dialect属性,你可以自定义jOOQ使用的SQL方言(dialect)。例如,设置方言为Postgres:

spring.jooq.sql-dialect=Postgres

定义自己的@Bean,在jOOQConfiguration创建时使用,可以实现更高级的定制。你可以为以下jOOQ类型定义beans:

  • ConnectionProvider
  • TransactionProvider
  • RecordMapperProvider
  • RecordListenerProvider
  • ExecuteListenerProvider
  • VisitListenerProvider

如果想全面控制jOOQ配置,你甚至可以创建自己的org.jooq.Configuration @Bean