Spring 之数据源
# Spring 之数据源
本文基于 Spring Boot 2.7.3 版本。
# Spring Boot 数据源基本配置
Spring Boot 提供了一系列 spring.datasource.*
配置来控制 DataSource
的配置。用户可以在 application.properties
或 application.yml
文件中指定数据源配置。这些配置项维护在 DataSourceProperties
(opens new window) 。
下面是一个最基本的 mysql 数据源配置示例(都是必填项):
# 数据库访问地址
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 (opens new window)、HSQL (opens new window) 和 Derby (opens new window)。使用者无需提供任何连接 URL,只需要包含对要使用的嵌入式数据库的构建依赖项。如果类路径上有多个嵌入式数据库,需要设置 spring.datasource.embedded-database-connection
配置属性来控制使用哪一个。将该属性设置为 none 会禁用嵌入式数据库的自动配置。
注意:如果在测试中使用此功能,无论使用多少应用程序上下文,整个测试套件都会重用同一个数据库。如果要确保每个上下文都有一个单独的嵌入式数据库,则应将
spring.datasource.generate-unique-name
设置为 true。
下面,通过一个实例展示如何连接 H2 嵌入式数据库。
(1)在 pom.xml 中引入所需要的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
(2)数据源配置
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 连接池化数据源
在生产环境中,出于性能考虑,一般会通过数据库连接池连接数据源。
除了 DataSourceProperties
(opens new window) 中的数据源通用配置以外,Spring Boot 还支持通过使用类似spring.datasource.hikari.*
、spring.datasource.tomcat.*
、spring.datasource.dbcp2.*
和 spring.datasource.oracleucp.*
的前缀来配置指定的数据库连接池属性。
下面,就是一份 hikari 的连接池配置示例:
# 连接池名称
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 (opens new window))选择需要的组件来创建一个 Spring Boot 工程。或者,直接在 pom.xml 中引入所需要的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
# 测试单数据源连接
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 (opens new window)
Spring Boot 连接多数据源所需要的依赖并无不同,主要差异在于数据源的配置。Spring Boot 默认的数据源配置类为 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
。使用者只要指定一些必要的 spring.datasource 配置,DataSourceAutoConfiguration
类就会自动完成剩下的数据源实例化工作。
# 多数据源配置
下面的示例中,自定义了一个数据源配置类,通过读取不同的 spring.datasource.xxx 来完成对于不同数据源的实例化工作。对于 JDBC 来说,最重要的,就是实例化 DataSource
和 JdbcTemplate
。
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
所指定的配置前缀进行配置:
# 数据源一: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 =
# 测试多数据源连接
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 中引入所需要的依赖:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
</project>
# Spring 配置数据源
Spring 配置数据源有多种方式,下面一一列举:
# 使用 JNDI 数据源
如果 Spring 应用部署在支持 JNDI 的 WEB 服务器上(如 WebSphere、JBoss、Tomcat 等),就可以使用 JNDI 获取数据源。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd">
<!-- 1.使用bean配置jndi数据源 -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/orclight" />
</bean>
<!-- 2.使用jee标签配置jndi数据源,与1等价,但是需要引入命名空间 -->
<jee:jndi-lookup id="dataSource" jndi-name=" java:comp/env/jdbc/orclight" />
</beans>
# 使用数据库连接池
Spring 本身并没有提供数据库连接池的实现,需要自行选择合适的数据库连接池。下面是一个使用 Druid (opens new window) 作为数据库连接池的示例:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="10"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="testWhileIdle" value="true"/>
<!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
<!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->
<property name="defaultAutoCommit" value="true"/>
<!-- 验证连接有效与否的SQL,不同的数据配置不同 -->
<property name="validationQuery" value="select 1 "/>
<property name="filters" value="stat"/>
</bean>
# 基于 JDBC 驱动的数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
# 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、用户名、密码
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
(2)配置 JNDI
如果想要通过 JNDI 方式连接数据源,可以采用如下方式:
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
的源码如下(省略部分代码):
@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 连接池驱动包。如果想要替换其他数据库连接池,前提是必须先手动引入对应的连接池驱动包。
- 当满足