从 spring-boot-starter-jdbc 到 DataSource

JDBC 是什么

JDBC 是 Java DataBase Connectivity 的缩写,是由一组用 Java 语言编写的类和接口,用于在 Java 应用程序中与数据库进行交互。

JDBC 只是一套标准规范,具体的实现由各个数据库厂商去实现。对开发者来说其屏蔽了不同数据库之间的区别,可以使用相同的方式(Java API)去操作不同的数据库。不同数据库厂商对 JDBC 的实现就是常说的数据库驱动。如 mysql-connector-java 是连接 MySQL 数据库的驱动。

如何使用 JDBC

大致步骤:

  1. 加载驱动
  2. 获取连接
  3. 执行 SQL
  4. 处理结果
  5. 释放资源

如果你使用 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。主要有三个功能:

  1. 提供了 DataSource 的自动配置
  2. 提供了 JdbcTemplate 等工具类来简化 JDBC 操作
  3. 提供了事务管理

下面主要关注数据源的自动配置。

在项目中引入 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 自动配置原理探究

posted @ 2024-12-18 22:05  Higurashi-kagome  阅读(10)  评论(0编辑  收藏  举报