从 spring-boot-starter-jdbc 到 DataSource
JDBC 是什么
JDBC 是 Java DataBase Connectivity 的缩写,是由一组用 Java 语言编写的类和接口,用于在 Java 应用程序中与数据库进行交互。
JDBC 只是一套标准规范,具体的实现由各个数据库厂商去实现。对开发者来说其屏蔽了不同数据库之间的区别,可以使用相同的方式(Java API)去操作不同的数据库。不同数据库厂商对 JDBC 的实现就是常说的数据库驱动。如 mysql-connector-java 是连接 MySQL 数据库的驱动。
如何使用 JDBC
大致步骤:
- 加载驱动
- 获取连接
- 执行 SQL
- 处理结果
- 释放资源
如果你使用 Maven,可以在 pom.xml 文件中添加驱动依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
然后如下使用 JDBC:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcExample {
public static void main(String[] args) {
// 数据库 URL
String url = "jdbc:mysql://localhost:3306/test";
// 数据库用户名和密码
String user = "root";
String password = "123456";
// 声明连接和其他对象
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 1. 加载 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 创建连接
connection = DriverManager.getConnection(url, user, password);
// 3. 创建 Statement 对象
statement = connection.createStatement();
// 4. 执行查询
String sql = "SELECT * FROM user";
resultSet = statement.executeQuery(sql);
// 5. 处理结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("ID: " + id + ", Name: " + name);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
try {
if (resultSet != null) resultSet.close();
if (statement != null) statement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
DataSource 接口
DataSource 接口(javax.sql.DataSource)是 JDBC 2.0 引入的,用于表示数据库连接工厂。功能上,可以把 DriverManager 看作是 JDBC 官方提供的 DataSource 实现(虽然实际上 DriverManager 并没有实现 DataSource 接口)。
DataSource 的常见实现有 DBCP、C3P0、HikariCP、Druid 等,这些实现通常使用了池化技术来提高连接利用率。
spring-boot-starter-jdbc 数据源配置
spring-boot-starter-jdbc 是 Spring Boot 提供的用于简化 JDBC 操作的 starter。主要有三个功能:
- 提供了 DataSource 的自动配置
- 提供了 JdbcTemplate 等工具类来简化 JDBC 操作
- 提供了事务管理
下面主要关注数据源的自动配置。
在项目中引入 spring-boot-starter-jdbc 后,会自动配置一个 DataSource 的 Bean,并提供一个 JdbcTemplate 的 Bean:
@Slf4j
@Component
public class Init implements CommandLineRunner {
@Resource
private DataSource dataSource;
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public void run(String... args) {
log.info(dataSource.getClass().getName());
log.info(jdbcTemplate.getClass().getName());
}
}
com.zaxxer.hikari.HikariDataSource
org.springframework.jdbc.core.JdbcTemplate
可以看到,DataSource 的默认实现类是 HikariDataSource。也就是 Spring Boot 默认使用了 HikariCP 作为连接池。
进入 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,其中的 PooledDataSourceConfiguration 会根据配置文件中的 spring.datasource.type 属性来决定使用哪个 DataSource 实现类(注意 @Import 注解):
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@AutoConfigureBefore(SqlInitializationAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
// ...
@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 {
}
// ...
}
比如打开 DataSourceConfiguration.Hikari 可以看到:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
因为 Spring Boot 默认引入了 HikariCP,而没有引入其他连接池的依赖比如 DBCP2,所以在 spring.datasource.type 没有配置的情况下,会使用 HikariDataSource。
再看上面的 createDataSource 方法,该方法会根据配置文件中配置的 url 等属性来创建 HikariDataSource。
protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
public DataSourceBuilder<?> initializeDataSourceBuilder() {
return DataSourceBuilder.create(getClassLoader()).type(getType()).driverClassName(determineDriverClassName())
.url(determineUrl()).username(determineUsername()).password(determinePassword());
}
要切换其他连接池,比如 Druid,可以引入 Druid 的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
然后在配置文件中指定 spring.datasource.type 为 com.alibaba.druid.pool.DruidDataSource:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
如果项目中使用了 dynamic-datasource,进入其自动配置类 DynamicDataSourceAutoConfiguration,可以看到其会在容器中注册 DynamicRoutingDataSource:
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
DynamicRoutingDataSource 继承自 AbstractRoutingDataSource,AbstractRoutingDataSource 继承自 AbstractDataSource,AbstractDataSource 实现了 DataSource 接口。
DynamicRoutingDataSource 是能够通过 @DS 注解切换不同的数据源的底层核心。
参考:从 jdbc 到 spring-boot-starter-jdbc、什么是 DataSource?什么又是 DruidDataSource?、SpringBoot 2.X 集成 jdbc 自动配置原理探究