Springboot基础知识(14)- JDBC访问数据库、数据源配置原理


1. JDBC访问数据库

    对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 都默认采用整合 Spring Data 的方式进行统一处理,通过大量自动配置,来简化对数据访问层的操作,只需要进行简单的设置即可实现对数据层的访问。

    本文将在 “ Springboot基础知识(08)- spring-boot-starter-web(Web启动器)” 里 SpringbootWeb 项目基础上,对 JDBC 进行讲解。

    1) 导入 JDBC 和 MariaDB 相关依赖包

        访问 http://www.mvnrepository.com/,查询 spring-boot-starter-data-jdbc 等

        修改 pom.xml:

复制代码
 1             <project ... >
 2                 ...
 3 
 4                 <dependencies>
 5                     ...
 6 
 7                     <!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
 8                     <dependency>
 9                         <groupId>org.mariadb.jdbc</groupId>
10                         <artifactId>mariadb-java-client</artifactId>
11                     </dependency>
12 
13                     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jdbc -->
14                     <dependency>
15                         <groupId>org.springframework.boot</groupId>
16                         <artifactId>spring-boot-starter-data-jdbc</artifactId>
17                     </dependency>
18 
19                     ...
20                 </dependencies>
21             
22                 ...    
23             </project>
复制代码


            注:Spring Boot 默认为数据库驱动程序做了版本仲裁,所以我们在导入数据库驱动时,可以不再声明版本。需要注意的是,数据库驱动的版本必须与数据库的版本相对应。

        在IDE中项目列表 -> SpringbootWeb -> 点击鼠标右键 -> Maven -> Reload Project

    2) 配置数据源

        可以在配置文件(application.properties/application.yml)中配置数据源,示例代码(application.properties)如下。

            # 数据源连接信息
            spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
            spring.datasource.url=jdbc:mysql://localhost:3306/testdb
            spring.datasource.username=root
            spring.datasource.password=123456

    3)配置数据库(MariaDB)

        (1) XAMPP for Windows

            https://www.apachefriends.org/download.html

            本文安装版本 7.4.25,用到 XAMPP 的如下功能:

                + Apache 2.4.51
                + MariaDB 10.4.21 (MySQL的分支)
                + PHP 7.4.25 (VC15 X86 64bit thread safe) + PEAR
                + phpMyAdmin 5.1.1

        (2) 创建数据库 testdb 和 user 表,SQL 脚本如下

            CREATE TABLE `user` (
            `id` int(11) NOT NULL,
            `name` varchar(50) NOT NULL,
            `password` varchar(255) DEFAULT NULL,
            `age` int(11) DEFAULT NULL,
            `createtime` timestamp NULL DEFAULT NULL
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

            INSERT INTO `user` (`id`, `name`, `password`, `age`, `createtime`) VALUES
            (1, 'admin', '123456', 21, '2020-01-01 01:01:01'); 

    4) 使用 JdbcTemplate

        Spring Boot 提供了一个名为 JdbcTemplate 的轻量级数据访问工具,它是对 JDBC 的封装。Spring Boot 对 JdbcTemplate 提供了默认自动配置,我们可以直接使用 @Autowired 或构造函数将它注入到 bean 中使用。
            
        创建 src/test/com/example/SpringBootJDBCTest.java 文件

复制代码
 1             package com.example;
 2 
 3             import javax.sql.DataSource;
 4             import java.sql.SQLException;
 5 
 6             import org.junit.jupiter.api.Test;
 7             import org.springframework.beans.factory.annotation.Autowired;
 8             import org.springframework.boot.test.context.SpringBootTest;
 9             import org.springframework.jdbc.core.JdbcTemplate;
10 
11             @SpringBootTest
12             public class SpringBootJDBCTest {
13                 @Autowired
14                 DataSource dataSource;  // 数据源组件
15                 @Autowired
16                 JdbcTemplate jdbcTemplate;  // 用于访问数据库的组件
17 
18                 @Test
19                 void contextLoads() throws SQLException {
20                     System.out.println("DataSource:" + dataSource.getClass());
21                     System.out.println("DataSource Connection:" + dataSource.getConnection());
22 
23                     Integer i = jdbcTemplate.queryForObject("SELECT count(*) from `user`", Integer.class);
24                     System.out.println("Table user has " + i + " record");
25                 }
26             }
复制代码


    运行该测试代码,结果如下:

        DataSource:class com.zaxxer.hikari.HikariDataSource
        DataSource Connection:HikariProxyConnection@247334525 wrapping org.mariadb.jdbc.MariaDbConnection@3a4ab7f7
        Table user has 1 record


2. 数据源配置原理

    在数据库访问过程中,“数据源” 是最重要的概念之一,它不仅可以对与数据库访问相关的各种参数进行封装和统一管理,还可以管理数据库连接池,提高数据库连接性能。

    目前,在市面上有很多优秀的开源数据源,例如 DBCP、C3P0、Druid、HikariCP 等等。在 Spring Boot 2.x 中,则采用目前性能最佳的 HikariCP 作为其默认数据源。接下来,我们就来具体介绍下 Spring Boot 的默认数据源配置及其原理。


    1) DataSourceAutoConfiguration


        Spring Boot 中几乎所有的默认配置都是通过配置类 XxxAutoConfiguration 进行配置的,Spring Boot 数据源也不例外,它的自动配置类是:DataSourceAutoConfiguration。

        DataSourceAutoConfiguration 中共包括以下 5 个内部静态类:

            (1) EmbeddedDatabaseCondition
            (2) PooledDataSourceAvailableCondition
            (3) PooledDataSourceCondition
            (4) PooledDataSourceConfiguration(池化数据源自动配置类)
            (5) EmbeddedDatabaseConfiguration(内嵌数据源自动配置类)

        其中,PooledDataSourceConfiguration 和 EmbeddedDatabaseConfiguration 为使用了 @Configuration 注解的自动配置类,其余 3 个为限制条件类。

    2) EmbeddedDatabaseConfiguration

        EmbeddedDatabaseConfiguration 是内嵌数据源的自动配置类,该类中并没有任何的方法实现,它的主要功能都是通过 @Import 注解引入 EmbeddedDataSourceConfiguration 类来实现的。

            @Import({EmbeddedDataSourceConfiguration.class})

        EmbeddedDataSourceConfiguration 向容器中添加了一个 Spring Boot 内嵌的数据源,该数据源支持 HSQL,H2 和 DERBY 三种数据库,其部分代码如下。

复制代码
 1             @Configuration(proxyBeanMethods = false)
 2             @EnableConfigurationProperties({DataSourceProperties.class})
 3             public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
 4                 private ClassLoader classLoader;
 5                 
 6                 public EmbeddedDataSourceConfiguration() {
 7                 }
 8 
 9                 public void setBeanClassLoader(ClassLoader classLoader) {
10                     this.classLoader = classLoader;
11                 }
12 
13                 //向容器中添加 Spring Boot 内嵌的数据源
14                 @Bean(destroyMethod = "shutdown")
15                 public EmbeddedDatabase dataSource(DataSourceProperties properties) {
16                     return (new EmbeddedDatabaseBuilder())
17                             .setType(EmbeddedDatabaseConnection.get(this.classLoader).getType())
18                             .setName(properties.determineDatabaseName()).build();
19                 }
20             }
复制代码


        通过上面的分析,自动配置类 EmbeddedDatabaseConfiguration 的作用是向容器中添加一个内嵌的数据源(DataSource),但这是有条件限制的。

        在 EmbeddedDatabaseConfiguration 类上还使用一个 @Conditional 注解,该注解使用了 DataSourceAutoConfiguration 的内部限制条件类 EmbeddedDatabaseCondition 来进行条件判断。

            @Conditional({DataSourceAutoConfiguration.EmbeddedDatabaseCondition.class})

        EmbeddedDatabaseCondition 主要用来检测容器中是否已经存在池化数据源(PooledDataSource)。若容器中存在池化数据源时,则 EmbeddedDatabaseConfiguration 不能被实例化。只有当容器中不存在池化数据源时,EmbeddedDatabaseConfiguration 才能被实例化,才能向容器中添加内嵌数据源(EmbeddedDataSource)。

    3) PooledDataSourceConfiguration

        PooledDataSourceConfiguration 是池化数据源的自动配置类,该类上使用了一个 @Conditional 注解,该注解使用了 DataSourceAutoConfiguration 的内部限制条件类 PooledDataSourceCondition 来进行条件判断。

            @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})

        PooledDataSourceCondition 与 EmbeddedDatabaseCondition 一样,也是用来检测容器中是否已经存在池化数据源的,但不同的是,PooledDataSourceConfiguration 是只有当容器中存在池化数据源时, 才可以被实例化,才可以向容器中添加池化数据源。

        与 EmbeddedDatabaseConfiguration 一样,PooledDataSourceConfiguration  类中也没有任何的方法实现,它的所有功能都是通过 @Import 注解引入其他的类实现的。

            @Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})

        PooledDataSourceConfiguration  通过 @Import 注解引入了 Hikari、Tomcat、Dbcp2、OracleUcp 和 Generic 五个数据源配置类,它们都是 DataSourceConfiguration 的内部类,且它们的功能类似,都是向容器中添加指定的数据源。

        下面以 Hikari 为例进行分析,Hikari 的源码如下。

复制代码
 1             @Configuration(proxyBeanMethods = false)
 2             @ConditionalOnClass({HikariDataSource.class})
 3             @ConditionalOnMissingBean({DataSource.class})
 4             @ConditionalOnProperty(
 5                 name = {"spring.datasource.type"},
 6                 havingValue = "com.zaxxer.hikari.HikariDataSource",
 7                 matchIfMissing = true
 8             )
 9             static class Hikari {
10                 Hikari() {
11                 }
12                 @Bean
13                 @ConfigurationProperties(prefix = "spring.datasource.hikari")
14                 HikariDataSource dataSource(DataSourceProperties properties) {
15                     HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
16                     if (StringUtils.hasText(properties.getName())) {
17                         dataSource.setPoolName(properties.getName());
18                     }
19                     return dataSource;
20                 }
21             }
复制代码


        在 Hikari 类中,主要使用以下注解:

            (1) @Configuration:表示当前类是一个配置类;
            (2) @ConditionalOnMissingBean({DataSource.class}):表示容器中没有用户自定义的数据源时,该配置类才会被实例化;
            (3) @ConditionalOnClass({HikariDataSource.class}):表示必须在类路径中存在 HikariDataSource 类时,Hikari 才会实例化。而 HikariDataSource 类是由 spring- boot-starter-jdbc 默认将其引入的,因此只要我们在 pom.xml 中引入了该 starter, Hikari 就会被实例化(这也是 Spring Boot 2.x 默认使用 HikariCP 作为其数据源的原因);
            (4) @ConditionalOnProperty(name = {"spring.datasource.type"}, havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true): 表示当 Spring Boot 配置文件中,配置了 spring.datasource.type = com.zaxxer.hikari.HikariDataSource(明确指定使用 Hikari 数据源)或者不配置 spring.datasource.type(即默认情况)时,Hikari 才会被实例化。

        Hikari 类通过 @Bean 注解向容器中添加了 HikariDataSource 组件,该组件的实例对象是通过调用 DataSourceConfiguration 的 createDataSource() 方法得到的,代码如下。

复制代码
1             @Bean
2             @ConfigurationProperties(prefix = "spring.datasource.hikari")
3             HikariDataSource dataSource(DataSourceProperties properties) {
4                 HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
5                 if (StringUtils.hasText(properties.getName())) {
6                     dataSource.setPoolName(properties.getName());
7                 }
8                 return dataSource;
9             }
复制代码


        在 createDataSource() 方法中,调用 DataSourceProperties 的 initializeDataSourceBuilder() 来初始化 DataSourceBuilder,源码如下。

            protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
                return properties.initializeDataSourceBuilder().type(type).build();
            }

        initializeDataSourceBuilder() 方法通过调用 DataSourceBuilder 的 create() 方法创建 DataSourceBuilder 对象,并根据 Spring Boot 的配置文件(application.properties/yml)中的配置,依次设置数据源类型、驱动类名、连接 url、 用户名和密码等信息。

            public DataSourceBuilder<?> initializeDataSourceBuilder() {
                return DataSourceBuilder.create(this.getClassLoader()).type(this.getType()).
                    driverClassName(this.determineDriverClassName()).url(this.determineUrl()).username(this.determineUsername()).password(this.determinePassword());
            }
 
        上面提到 spring.datasource.type 默认是可以不用配置的,因此在 createDataSource() 方法在获取到回传回来的 DataSourceBuilder 对象后,还需要将其 type 属性再次设置为 HikariDataSource,并调用 DataSourceBuilder 的 build() 方法,完成 HikariDataSource 的初始化。

        dataSource() 方法获得数据源对象,并设置了连接池的名字(name),注入到容器中。

        自此,就完成了对 Spring Boot 数据源自动配置原理的分析。


    4) 总结

        通过对 Spring Boot 数据源自动配置原理的分析可知:

        (1) 在用户没有配置数据源的情况,若容器中存在 HikariDataSource 类,则 Spring Boot 就会自动实例化 Hikari,并将其作为其数据源。
        (2) Spring Boot 的 spring-boot-starter-data-jdbc 通过 spring- boot-starter-jdbc 默认引入了 HikariCP 数据源(包含 HikariDataSource 类),因此 Spring Boot 默认使用 HikariCP 作为其数据源。



posted @   垄山小站  阅读(748)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
点击右上角即可分享
微信分享提示