Skip to content

Spring Boot 数据访问配置指南 📚

概述

在现代企业级应用开发中,数据访问层是系统的核心组成部分。Spring Boot 通过其强大的自动配置机制,极大地简化了数据源配置、JPA 设置以及各种数据访问技术的集成。本文将深入探讨 Spring Boot 中数据访问的各种配置方式,帮助开发者掌握从基础到高级的数据访问配置技巧。

NOTE

Spring Boot 的数据访问配置遵循"约定优于配置"的原则,在提供合理默认值的同时,也允许开发者进行细粒度的自定义配置。

为什么需要自定义数据源配置?

在实际项目中,我们经常遇到以下场景:

  • 多数据源需求:读写分离、分库分表
  • 特定连接池配置:性能调优、监控需求
  • 不同环境配置:开发、测试、生产环境的差异化配置
  • 安全性要求:加密连接、证书认证

1. 配置自定义数据源 ⚙️

1.1 基础自定义数据源

Spring Boot 允许我们通过 @Bean 注解来定义自定义的 DataSource,从而完全控制数据源的配置。

kotlin
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource") 
    fun dataSource(): DataSource {
        return DataSourceBuilder.create().build() 
    }
}
kotlin
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource")
    fun dataSource(): SomeDataSource {
        return SomeDataSource() 
    }
}

配置文件示例:

yaml
app:
  datasource:
    url: "jdbc:h2:mem:mydb"
    username: "sa"
    pool-size: 30

TIP

使用 @ConfigurationProperties 注解可以将配置文件中的属性自动绑定到数据源对象的属性上,这种方式比硬编码更加灵活。

1.2 使用 HikariCP 连接池

HikariCP 是 Spring Boot 默认的连接池实现,以其高性能和轻量级著称。

kotlin
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource")
    fun dataSource(): HikariDataSource {
        return DataSourceBuilder.create()
            .type(HikariDataSource::class.java) 
            .build()
    }
}

WARNING

注意 HikariCP 使用 jdbc-url 而不是 url 属性,配置时需要相应调整。

yaml
app:
  datasource:
    jdbc-url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    pool-size: 30

1.3 使用 DataSourceProperties 处理属性转换

为了解决不同连接池实现之间属性名差异的问题,Spring Boot 提供了 DataSourceProperties 类:

kotlin
@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource")
    fun dataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    @ConfigurationProperties("app.datasource.configuration")
    fun dataSource(properties: DataSourceProperties): HikariDataSource {
        return properties.initializeDataSourceBuilder() 
            .type(HikariDataSource::class.java)
            .build()
    }
}

这样配置后,可以使用标准的 url 属性:

yaml
app:
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    configuration:
      maximum-pool-size: 30

2. 配置多数据源 🔄

在微服务架构或复杂业务场景中,单一数据源往往无法满足需求。Spring Boot 支持配置多个数据源来应对不同的业务场景。

2.1 多数据源配置原理

2.2 配置第二个数据源

kotlin
@Configuration(proxyBeanMethods = false)
class MyAdditionalDataSourceConfiguration {

    @Qualifier("second") 
    @Bean(defaultCandidate = false) 
    @ConfigurationProperties("app.datasource")
    fun secondDataSource(): HikariDataSource {
        return DataSourceBuilder.create()
            .type(HikariDataSource::class.java)
            .build()
    }
}

IMPORTANT

  • @Qualifier("second") 用于标识这个特定的数据源
  • defaultCandidate = false 防止与自动配置的数据源冲突

2.3 使用多数据源

kotlin
@Service
class UserService(
    private val primaryDataSource: DataSource, 
    @Qualifier("second") private val secondaryDataSource: DataSource
) {
    
    fun processUserData() {
        // 使用主数据源进行核心业务操作
        // 使用辅助数据源进行日志或统计操作
    }
}

配置文件示例:

yaml
# 主数据源(Spring Boot 自动配置)
spring:
  datasource:
    url: "jdbc:mysql://localhost/primary"
    username: "primary_user"
    password: "primary_pass"

# 辅助数据源
app:
  datasource:
    url: "jdbc:mysql://localhost/secondary"
    username: "secondary_user"
    password: "secondary_pass"

3. Spring Data Repository 配置 📁

3.1 Repository 自动发现机制

Spring Boot 会自动扫描并创建 Repository 接口的实现类:

kotlin
// 实体类
@Entity
data class User(
    @Id @GeneratedValue
    val id: Long = 0,
    val name: String,
    val email: String
)

// Repository 接口
interface UserRepository : JpaRepository<User, Long> {
    fun findByEmail(email: String): User?
    fun findByNameContaining(name: String): List<User>
}

// 服务类
@Service
class UserService(
    private val userRepository: UserRepository
) {
    
    fun createUser(name: String, email: String): User {
        val user = User(name = name, email = email)
        return userRepository.save(user) 
    }
    
    fun findUserByEmail(email: String): User? {
        return userRepository.findByEmail(email) 
    }
}

3.2 自定义 Repository 扫描路径

kotlin
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = [User::class]) 
class MyApplication

4. JPA 属性配置 🔧

4.1 常用 JPA 配置

yaml
spring:
  jpa:
    hibernate:
      ddl-auto: validate
      naming:
        physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        jdbc:
          batch_size: 20

WARNING

spring.jpa.properties.* 下的属性名必须与 JPA 提供者期望的名称完全匹配,Spring Boot 不会进行任何形式的属性名转换。

4.2 自定义 Hibernate 属性

kotlin
@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {

    @Bean
    fun hibernatePropertiesCustomizer(): HibernatePropertiesCustomizer {
        return HibernatePropertiesCustomizer { properties ->
            properties["hibernate.jdbc.batch_size"] = 25
            properties["hibernate.order_inserts"] = true
            properties["hibernate.order_updates"] = true
        }
    }
}

5. 高级配置场景 🚀

5.1 多 EntityManagerFactory 配置

在需要连接多个数据库且使用不同 JPA 配置的场景下:

完整的多 EntityManagerFactory 配置示例
kotlin
@Configuration(proxyBeanMethods = false)
class MyAdditionalEntityManagerFactoryConfiguration {

    @Qualifier("second")
    @Bean(defaultCandidate = false)
    @ConfigurationProperties("app.jpa")
    fun secondJpaProperties(): JpaProperties {
        return JpaProperties()
    }

    @Qualifier("second")
    @Bean(defaultCandidate = false)
    fun secondEntityManagerFactory(
        @Qualifier("second") dataSource: DataSource,
        @Qualifier("second") jpaProperties: JpaProperties
    ): LocalContainerEntityManagerFactoryBean {
        val builder = createEntityManagerFactoryBuilder(jpaProperties)
        return builder.dataSource(dataSource)
            .packages(Order::class.java)
            .persistenceUnit("second")
            .build()
    }

    private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
        val jpaVendorAdapter = HibernateJpaVendorAdapter()
        val jpaPropertiesFactory = { dataSource: DataSource ->
            createJpaProperties(dataSource, jpaProperties.properties)
        }
        return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null)
    }

    private fun createJpaProperties(dataSource: DataSource, existingProperties: Map<String, *>): Map<String, *> {
        val jpaProperties: MutableMap<String, Any> = LinkedHashMap(existingProperties)
        // 根据 DataSource 配置特定的 JPA 属性
        return jpaProperties
    }
}

5.2 Repository 配置

kotlin
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
    basePackageClasses = [Order::class], 
    entityManagerFactoryRef = "secondEntityManagerFactory"
)
class OrderConfiguration

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
    basePackageClasses = [Customer::class], 
    entityManagerFactoryRef = "entityManagerFactory"
)
class CustomerConfiguration

6. 实际应用场景 💡

6.1 读写分离配置

kotlin
@Configuration(proxyBeanMethods = false)
class ReadWriteSplitConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties("spring.datasource.write")
    fun writeDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Bean
    @ConfigurationProperties("spring.datasource.read")
    fun readDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }
}
yaml
spring:
  datasource:
    write:
      url: "jdbc:mysql://write-db:3306/myapp"
      username: "write_user"
      password: "write_pass"
    read:
      url: "jdbc:mysql://read-db:3306/myapp"
      username: "read_user"
      password: "read_pass"

6.2 条件化数据源配置

kotlin
@Configuration(proxyBeanMethods = false)
class ConditionalDataSourceConfiguration {

    @Bean
    @ConditionalOnProperty(name = ["app.database.type"], havingValue = "mysql")
    @ConfigurationProperties("app.datasource.mysql")
    fun mysqlDataSource(): DataSource {
        return DataSourceBuilder.create()
            .driverClassName("com.mysql.cj.jdbc.Driver")
            .build()
    }

    @Bean
    @ConditionalOnProperty(name = ["app.database.type"], havingValue = "postgresql")
    @ConfigurationProperties("app.datasource.postgresql")
    fun postgresqlDataSource(): DataSource {
        return DataSourceBuilder.create()
            .driverClassName("org.postgresql.Driver")
            .build()
    }
}

7. 最佳实践建议 ⭐

7.1 配置管理

配置文件组织

  • 使用 profile 区分不同环境的配置
  • 敏感信息使用环境变量或配置中心
  • 合理使用 @ConfigurationProperties 进行类型安全的配置绑定

7.2 性能优化

连接池配置

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      max-lifetime: 1200000
      connection-timeout: 20000

7.3 监控和诊断

kotlin
@Configuration(proxyBeanMethods = false)
class DataSourceMonitoringConfiguration {

    @Bean
    fun dataSourceHealthIndicator(dataSource: DataSource): DataSourceHealthIndicator {
        return DataSourceHealthIndicator(dataSource, "SELECT 1")
    }
}

总结

Spring Boot 的数据访问配置体系为开发者提供了从简单到复杂的全方位解决方案。通过理解其自动配置机制和扩展点,我们可以:

灵活配置单一或多个数据源
实现读写分离和分库分表
优化连接池性能
集成多种数据访问技术

NOTE

在实际项目中,建议从简单配置开始,根据业务需求逐步引入复杂配置。记住,过度配置可能会增加系统复杂性,选择合适的配置策略是关键。

通过本文的学习,相信你已经掌握了 Spring Boot 数据访问配置的核心技能。在实际开发中,可以根据具体需求选择合适的配置方式,构建高效、可维护的数据访问层。