--- title: Spring 之数据源 date: 2017-10-20 09:27:55 order: 01 categories: - Java - 框架 - Spring - Spring数据 tags: - Java - 框架 - Spring - SpringBoot - 数据库 - DataSource permalink: /pages/0d7fe974/ --- # Spring 之数据源 > 本文基于 Spring Boot 2.7.3 版本。 ## Spring Boot 数据源基本配置 Spring Boot 提供了一系列 `spring.datasource.*` 配置来控制 `DataSource` 的配置。用户可以在 `application.properties` 或 `application.yml` 文件中指定数据源配置。这些配置项维护在 [`DataSourceProperties`](https://github.com/spring-projects/spring-boot/tree/v2.7.4/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java) 。 下面是一个最基本的 mysql 数据源配置示例(都是必填项): ```properties # 数据库访问地址 spring.datasource.url = jdbc:mysql://localhost:3306/spring_tutorial?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8 # 数据库驱动类,必须保证驱动类是可加载的 spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver # 数据库账号 spring.datasource.username = root # 数据库账号密码 spring.datasource.password = root ``` 需要根据实际情况,替换 `url`、`username`、`password`。 ## Spring Boot 连接嵌入式数据源 使用内存嵌入式数据库开发应用程序通常很方便。显然,内存数据库不提供持久存储。使用者需要在应用程序启动时填充数据库,并准备在应用程序结束时丢弃数据。 Spring Boot 可以自动配置嵌入式数据库 [H2](https://www.h2database.com/)、[HSQL](https://hsqldb.org/) 和 [Derby](https://db.apache.org/derby/)。使用者无需提供任何连接 URL,只需要包含对要使用的嵌入式数据库的构建依赖项。如果类路径上有多个嵌入式数据库,需要设置 `spring.datasource.embedded-database-connection` 配置属性来控制使用哪一个。将该属性设置为 none 会禁用嵌入式数据库的自动配置。 > 注意:如果在测试中使用此功能,无论使用多少应用程序上下文,整个测试套件都会重用同一个数据库。如果要确保每个上下文都有一个单独的嵌入式数据库,则应将 `spring.datasource.generate-unique-name` 设置为 true。 下面,通过一个实例展示如何连接 H2 嵌入式数据库。 (1)在 pom.xml 中引入所需要的依赖: ```xml org.springframework.boot spring-boot-starter-data-jdbc com.h2database h2 ``` (2)数据源配置 ```properties spring.datasource.jdbc-url = jdbc:h2:mem:test spring.datasource.driver-class-name = org.h2.Driver spring.datasource.username = sa spring.datasource.password = ``` ## Spring Boot 连接池化数据源 > 完整示例:[spring-boot-data-jdbc](https://github.com/dunwu/spring-tutorial/tree/master/codes/data/spring-boot-data-jdbc) 在生产环境中,出于性能考虑,一般会通过数据库连接池连接数据源。 除了 [`DataSourceProperties`](https://github.com/spring-projects/spring-boot/tree/v2.7.4/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java) 中的数据源通用配置以外,Spring Boot 还支持通过使用类似`spring.datasource.hikari.*`、`spring.datasource.tomcat.*`、`spring.datasource.dbcp2.*` 和 `spring.datasource.oracleucp.*` 的前缀来配置指定的数据库连接池属性。 下面,就是一份 hikari 的连接池配置示例: ```properties # 连接池名称 spring.datasource.hikari.pool-name = SpringTutorialHikariPool # 最大连接数,小于等于 0 会被重置为默认值 10;大于零小于 1 会被重置为 minimum-idle 的值 spring.datasource.hikari.maximum-pool-size = 10 # 最小空闲连接,默认值10,小于 0 或大于 maximum-pool-size,都会重置为 maximum-pool-size spring.datasource.hikari.minimum-idle = 10 # 连接超时时间(单位:毫秒),小于 250 毫秒,会被重置为默认值 30 秒 spring.datasource.hikari.connection-timeout = 60000 # 空闲连接超时时间,默认值 600000(10分钟),大于等于 max-lifetime 且 max-lifetime>0,会被重置为0;不等于 0 且小于 10 秒,会被重置为 10 秒 # 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放 spring.datasource.hikari.idle-timeout = 600000 # 连接最大存活时间,不等于 0 且小于 30 秒,会被重置为默认值 30 分钟。该值应该比数据库所设置的超时时间短 spring.datasource.hikari.max-lifetime = 540000 ``` Spring Boot 会按以下顺序检测连接池是否可用,如果可用就选择对应的池化 `DataSource`: HikariCP -> Tomcat pooling DataSource -> DBCP2 -> Oracle UCP 用户也可以通过 `spring.datasource.type` 来指定数据源类型。 此外,也可以使用 `DataSourceBuilder` 手动配置其他连接池。如果自定义 DataSource bean,则不会发生自动配置。 `DataSourceBuilder` 支持以下连接池: - HikariCP - Tomcat pooling `Datasource` - Commons DBCP2 - Oracle UCP & `OracleDataSource` - Spring Framework’s `SimpleDriverDataSource` - H2 `JdbcDataSource` - PostgreSQL `PGSimpleDataSource` - C3P0 ### 引入 Spring Boot 依赖 你可以通过 Spring Boot 官方的初始化器([Spring Initializr](https://start.spring.io/))选择需要的组件来创建一个 Spring Boot 工程。或者,直接在 pom.xml 中引入所需要的依赖: ```xml org.springframework.boot spring-boot-starter-data-jdbc mysql mysql-connector-java ``` ### 测试单数据源连接 ```java import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Connection; import javax.sql.DataSource; @Slf4j @SpringBootApplication public class SpringBootDataJdbcApplication implements CommandLineRunner { private final JdbcTemplate jdbcTemplate; public SpringBootDataJdbcApplication(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public static void main(String[] args) { SpringApplication.run(SpringBootDataJdbcApplication.class, args); } @Override public void run(String... args) throws Exception { DataSource dataSource = jdbcTemplate.getDataSource(); Connection connection; if (dataSource != null) { connection = dataSource.getConnection(); } else { log.error("连接数据源失败!"); return; } if (connection != null) { log.info("数据源 Url: {}", connection.getMetaData().getURL()); } else { log.error("连接数据源失败!"); } } } ``` 运行 `main` 方法后,控制台会输出以下内容,表示数据源连接成功: ``` 20:50:18.449 [main] [INFO ] i.g.d.s.d.SpringBootDataJdbcApplication.run - 数据源 Url: jdbc:mysql://localhost:3306/spring_tutorial?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8 ``` ## Spring Boot 连接多数据源 > 完整示例:[spring-boot-data-jdbc-multi-datasource](https://github.com/dunwu/spring-tutorial/tree/master/codes/data/spring-boot-data-jdbc-multi-datasource) Spring Boot 连接多数据源所需要的依赖并无不同,主要差异在于数据源的配置。Spring Boot 默认的数据源配置类为 `org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration`。使用者只要指定一些必要的 spring.datasource 配置,`DataSourceAutoConfiguration` 类就会自动完成剩下的数据源实例化工作。 ### 多数据源配置 下面的示例中,自定义了一个数据源配置类,通过读取不同的 spring.datasource.xxx 来完成对于不同数据源的实例化工作。对于 JDBC 来说,最重要的,就是实例化 `DataSource` 和 `JdbcTemplate`。 ```java import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; @Configuration public class DataSourceConfig { @Primary @Bean("mysqlDataSource") @ConfigurationProperties(prefix = "spring.datasource.mysql") public DataSource mysqlDataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean("mysqlJdbcTemplate") public JdbcTemplate mysqlJdbcTemplate(@Qualifier("mysqlDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean("h2DataSource") @ConfigurationProperties(prefix = "spring.datasource.h2") public DataSource h2DataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "h2JdbcTemplate") public JdbcTemplate h2JdbcTemplate(@Qualifier("h2DataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } } ``` `application.properties` 或 `application.yml` 配置文件中也必须以 `@ConfigurationProperties` 所指定的配置前缀进行配置: ```properties # 数据源一:Mysql spring.datasource.mysql.jdbc-url = jdbc:mysql://localhost:3306/spring_tutorial?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.mysql.driver-class-name = com.mysql.cj.jdbc.Driver spring.datasource.mysql.username = root spring.datasource.mysql.password = root # 数据源一:H2 spring.datasource.h2.jdbc-url = jdbc:h2:mem:test spring.datasource.h2.driver-class-name = org.h2.Driver spring.datasource.h2.username = sa spring.datasource.h2.password = ``` ### 测试多数据源连接 ```java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; @SpringBootApplication public class SpringBootDataJdbcMultiDataSourceApplication implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(SpringBootDataJdbcMultiDataSourceApplication.class); private final UserDao mysqlUserDao; private final UserDao h2UserDao; public SpringBootDataJdbcMultiDataSourceApplication(@Qualifier("mysqlUserDao") UserDao mysqlUserDao, @Qualifier("h2UserDao") UserDao h2UserDao) { this.mysqlUserDao = mysqlUserDao; this.h2UserDao = h2UserDao; } public static void main(String[] args) { SpringApplication.run(SpringBootDataJdbcMultiDataSourceApplication.class, args); } @Override public void run(String... args) throws Exception { if (mysqlUserDao != null && mysqlUserDao.getJdbcTemplate() != null) { printDataSourceInfo(mysqlUserDao.getJdbcTemplate()); log.info("Connect to mysql datasource success."); } else { log.error("Connect to mysql datasource failed!"); return; } if (h2UserDao != null) { printDataSourceInfo(h2UserDao.getJdbcTemplate()); log.info("Connect to h2 datasource success."); } else { log.error("Connect to h2 datasource failed!"); return; } // 主数据源执行 JDBC SQL mysqlUserDao.recreateTable(); // 次数据源执行 JDBC SQL h2UserDao.recreateTable(); } private void printDataSourceInfo(JdbcTemplate jdbcTemplate) throws SQLException { DataSource dataSource = jdbcTemplate.getDataSource(); Connection connection; if (dataSource != null) { connection = dataSource.getConnection(); } else { log.error("Get dataSource failed!"); return; } if (connection != null) { log.info("DataSource Url: {}", connection.getMetaData().getURL()); } else { log.error("Connect to datasource failed!"); } } } ``` 运行 `main` 方法后,控制台会输出以下内容,表示数据源连接成功: ``` 21:16:44.654 [main] [INFO ] i.g.d.s.d.SpringBootDataJdbcMultiDataSourceApplication.printDataSourceInfo - DataSource Url: jdbc:mysql://localhost:3306/spring_tutorial?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false 21:16:44.654 [main] [INFO ] i.g.d.s.d.SpringBootDataJdbcMultiDataSourceApplication.run - Connect to mysql datasource success. 21:16:44.726 [main] [INFO ] i.g.d.s.d.SpringBootDataJdbcMultiDataSourceApplication.printDataSourceInfo - DataSource Url: jdbc:h2:mem:test 21:16:44.726 [main] [INFO ] i.g.d.s.d.SpringBootDataJdbcMultiDataSourceApplication.run - Connect to h2 datasource success. ``` ## Spring 之数据源 如果你的项目是传统的 Spring 项目,当然也可以轻松建立数据源连接,只是需要自行设置的配置更多一些。 ### 引入 Spring 依赖 在 pom.xml 中引入所需要的依赖: ```xml com.alibaba druid mysql mysql-connector-java org.springframework spring-context-support org.springframework spring-jdbc org.springframework spring-tx ``` ### Spring 配置数据源 Spring 配置数据源有多种方式,下面一一列举: #### 使用 JNDI 数据源 如果 Spring 应用部署在支持 JNDI 的 WEB 服务器上(如 WebSphere、JBoss、Tomcat 等),就可以使用 JNDI 获取数据源。 ```xml ``` #### 使用数据库连接池 Spring 本身并没有提供数据库连接池的实现,需要自行选择合适的数据库连接池。下面是一个使用 [Druid](https://github.com/alibaba/druid) 作为数据库连接池的示例: ```xml ``` #### 基于 JDBC 驱动的数据源 ```xml ``` ## SpringBoot 数据源配置 > Spring Boot 数据库配置官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.sql 通过前面的实战,我们已经知道了 Spring、Spring Boot 是如何连接数据源,并通过 JDBC 方式访问数据库。 SpringBoot 数据源的配置方式是在 `application.properties` 或 `application.yml` 文件中指定 `spring.datasource.*` 的配置。 (1)数据源基本配置方式是指定 url、用户名、密码 ```properties spring.datasource.url=jdbc:mysql://localhost/test spring.datasource.username=dbuser spring.datasource.password=dbpass ``` (2)配置 JNDI 如果想要通过 JNDI 方式连接数据源,可以采用如下方式: ```properties spring.datasource.jndi-name=java:jboss/datasources/customers ``` ## DataSourceAutoConfiguration 类 显而易见,Spring Boot 的配置更加简化,那么, Spring Boot 做了哪些工作,使得接入更加便捷呢?奥秘就在于 `spring-boot-autoconfigure` jar 包,其中定义了大量的 Spring Boot 自动配置类。其中,与数据库访问相关的比较核心的配置类有: - `DataSourceAutoConfiguration`:数据源自动配置类 - `JdbcTemplateAutoConfiguration`:`JdbcTemplate` 自动配置类 - `DataSourceTransactionManagerAutoConfiguration`:数据源事务管理自动配置类 - `JndiDataSourceAutoConfiguration`:JNDI 数据源自动配置类 - `EmbeddedDataSourceConfiguration`:嵌入式数据库数据源自动配置类 - 等等 这些自动配置类会根据各种条件控制核心类的实例化。 `DataSourceAutoConfiguration` 是数据源自动配置类,它负责实例化 `DataSource`。 `DataSourceAutoConfiguration` 的源码如下(省略部分代码): ```java @AutoConfiguration(before = SqlInitializationAutoConfiguration.class) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") @EnableConfigurationProperties(DataSourceProperties.class) @Import(DataSourcePoolMetadataProvidersConfiguration.class) public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration { } @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { } static class PooledDataSourceCondition extends AnyNestedCondition { // 略 } static class PooledDataSourceAvailableCondition extends SpringBootCondition { // 略 } static class EmbeddedDatabaseCondition extends SpringBootCondition { // 略 } } ``` `DataSourceAutoConfiguration` 类的源码解读: - `DataSourceProperties` 是 `DataSourceAutoConfiguration` 的配置选项类,允许使用者通过设置选项控制 `DataSource` 初始化行为。 - `DataSourceAutoConfiguration` 通过 `@Import` 注解引入 `DataSourcePoolMetadataProvidersConfiguration` 类。 - `DataSourceAutoConfiguration` 中定义了两个内部类:嵌入式数据源配置类 `EmbeddedDatabaseConfiguration` 和 池化数据源配置类 `PooledDataSourceConfiguration`,分别标记了不同的实例化条件。 - 当满足 `EmbeddedDatabaseConfiguration` 的示例化条件时,将引入 `EmbeddedDataSourceConfiguration` 类初始化数据源,这个类实际上是加载嵌入式数据源驱动的 ClassLoader 去进行初始化。 - 当满足 `PooledDataSourceConfiguration` 的示例化条件时,将引入 `DataSourceConfiguration.Hikari.class`、`DataSourceConfiguration.Tomcat.class`、`DataSourceConfiguration.Dbcp2.class`、`DataSourceConfiguration.OracleUcp.class`、`DataSourceConfiguration.Generic.class`、`DataSourceJmxConfiguration.class` 这些配置类,分别对应不同的数据库连接池方式。具体选用哪种数据库连接池,可以通过 `spring.datasource.type` 配置指定。其中,Hikari 是 Spring Boot 默认的数据库连接池,spring-boot-starter-data-jdbc 中内置了 Hikari 连接池驱动包。如果想要替换其他数据库连接池,前提是必须先手动引入对应的连接池驱动包。 ## 参考资料 - [Spring 官网](https://spring.io/) - [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html) - [Spring Boot 官方文档](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html)