1. 概述

Spring Boot 采用了一套“约定优于配置”的机制来扫描并自动配置 DataSource。这使得我们能够轻松地获得一个默认配置完整的 DataSource 实现。

此外,Spring Boot 还会自动配置一个高性能的连接池,它会按照优先级依次尝试使用 HikariCPApache TomcatCommons DBCP,具体取决于类路径中存在哪个依赖。

虽然 Spring Boot 的自动 DataSource 配置在大多数情况下表现良好,但有时候我们需要更精细的控制,这时候就需要我们手动创建 DataSource 实现,从而跳过 Spring Boot 的自动配置流程。

本文将带你学习 如何在 Spring Boot 中以编程方式配置 DataSource

2. Maven 依赖

以编程方式创建 DataSource 实现整体上是比较简单的

为了演示这一点,我们将实现一个简单的 Repository 层,用于对一些 JPA 实体执行 CRUD 操作。

下面是示例项目的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.4.1</version> 
    <scope>runtime</scope> 
</dependency>

如上所示,我们将使用一个内存中的 H2 数据库实例来测试 Repository 层。这样可以在不产生昂贵数据库操作开销的前提下,验证我们以编程方式配置的 DataSource 是否生效。

另外,请确保检查 Maven Central 上 spring-boot-starter-data-jpa 的最新版本。

3. 编程方式配置 DataSource

如果我们继续使用 Spring Boot 的自动 DataSource 配置并运行当前项目,它会按预期正常工作。

Spring Boot 会自动完成所有底层基础设施的配置工作。这包括创建 H2 的 DataSource 实现,由 HikariCP、Apache Tomcat 或 Commons DBCP 自动处理,并初始化一个内存数据库实例。

此外,我们甚至不需要创建 application.properties 文件,因为 Spring Boot 会提供一些默认的数据库设置。

但正如前面提到的,有时我们需要更高级别的定制化,因此需要以编程方式配置自己的 DataSource 实现。

最简单的方式是定义一个 DataSource 的工厂方法,并将其放在一个标注了 @Configuration 注解的类中

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource getDataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("org.h2.Driver");
        dataSourceBuilder.url("jdbc:h2:mem:test");
        dataSourceBuilder.username("SA");
        dataSourceBuilder.password("");
        return dataSourceBuilder.build();
    }
}

在这个例子中,我们使用了便捷的 DataSourceBuilder,它采用了 Joshua Bloch 的构建者模式(非流式风格),用于以编程方式创建自定义的 DataSource 对象

这种方式非常友好,因为构建器可以让我们轻松地使用一些常见属性来配置 DataSource,并且底层也会自动使用连接池。

4. 使用 application.properties 外部化 DataSource 配置

当然,我们也可以部分地将 DataSource 配置外部化。例如,我们可以在工厂方法中定义一些基础的 DataSource 属性:

@Bean 
public DataSource getDataSource() { 
    DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); 
    dataSourceBuilder.username("SA"); 
    dataSourceBuilder.password(""); 
    return dataSourceBuilder.build(); 
}

然后在 application.properties 文件中指定一些额外的配置:

spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driver-class-name=org.h2.Driver

定义在外部源(如上面的 application.properties 文件)中的属性,或通过标注 @ConfigurationProperties 的类定义的属性,将会覆盖 Java API 中的配置

⚠️ 这种方式的缺点是,我们的 DataSource 配置不再集中在一个地方。

✅ 但好处是,它可以将编译时和运行时的配置设置很好地分离,便于我们在不同环境下使用不同的配置,而无需修改 Java 配置代码。

5. 测试 DataSource 配置

测试我们自定义的 DataSource 配置非常简单。整个流程包括创建一个 JPA 实体、定义一个基本的 Repository 接口,并对 Repository 层进行测试。

5.1. 创建一个 JPA 实体

我们先定义一个示例 JPA 实体类,用于建模用户:

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String name;
    private String email;

    // 标准构造函数 / setter / getter / toString 方法
    
}

5.2. 简单的 Repository 层

接下来我们需要实现一个基本的 Repository 层,用于对上面定义的 User 实体类执行 CRUD 操作。

由于我们使用了 Spring Data JPA,无需从头实现 DAO。只需扩展 CrudRepository 接口即可:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

5.3. 测试 Repository 层

最后,我们需要验证我们以编程方式配置的 DataSource 是否正常工作。可以通过一个集成测试轻松完成:

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryIntegrationTest {
    
    @Autowired
    private UserRepository userRepository;
   
    @Test
    public void whenCalledSave_thenCorrectNumberOfUsers() {
        userRepository.save(new User("Bob", "[email protected]"));
        List<User> users = (List<User>) userRepository.findAll();
        
        assertThat(users.size()).isEqualTo(1);
    }    
}

UserRepositoryIntegrationTest 类非常直观,它调用了 Repository 接口的两个 CRUD 方法来持久化和查找实体。

✅ **无论我们选择完全以编程方式配置 DataSource,还是将其拆分为 Java 配置方法和 application.properties,我们都应该始终获得一个可用的数据库连接**。

5.4. 运行示例应用

最后,我们可以使用标准的 main() 方法运行示例应用:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("John", "[email protected]");
            User user2 = new User("Julie", "[email protected]");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(user -> System.out.println(user));
        };
    }
}

我们已经测试过 Repository 层,因此可以确定 DataSource 配置成功。运行示例应用后,应该在控制台输出中看到数据库中存储的 User 实体列表。

6. 总结

在本文中,我们学习了如何在 Spring Boot 中以编程方式配置 DataSource 实现

一如既往,本文中所有代码示例都可以在 GitHub 上找到。


原始标题:Configuring a DataSource Programmatically in Spring Boot