SpringBoot2.x 整合JDBC与数据源的配置原理

导读:本篇文章讲解 SpringBoot2.x 整合JDBC与数据源的配置原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

参考资料

SpringBoot官方文档#Working with SQL Databases

SpringBoot官方文档#Data Access

SpringBoot官方文档#Starters

HikariCP官方文档

Spring Boot默认HikariDataSource配置

默认数据源Hikari的配置详解

具体配置参考Druid Spring Boot Starter

springboot2.x整合JDBC与数据源配置原理解析

1 基本概念

1.1 JDBC

JDBC 的全称是Java数据库连接(Java Database Connectivity,简称JDBC),是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口

提供了诸如查询和更新数据库中数据的方法,JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的

JDBC API 主要位于JDK中的java.sql包中(之后扩展的内容位于 javax.sql 包中),主要包括(这几个代表接口,需驱动程序提供者来具体实现):

  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。

  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。

  • Connection:数据库连接,负责与进行数据库间通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。

  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。

  • PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。

  • CallableStatement:用以调用数据库中的存储过程。

  • SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

1.2 数据源

java.sql中并没有数据源(Data Source)的概念。这是由于在java.sql中包含的是JDBC内核API.

另外还有个javax.sql包,其中包含了JDBC标准的扩展API。而关于数据源(Data Source)的定义,就在javax.sql这个扩展包中。

实际上,在JDBC内核API的实现下,就已经可以实现对数据库的访问了,那么我们为什么还需要数据源呢?主要出于以下几个目的:

  • 封装关于数据库访问的各种参数,实现统一管理

  • 通过对数据库的连接池管理,节省开销并提高效率

在Java的生态环境中,已经有非常多优秀的开源数据源,比如:DBCP、C3P0、Druid、HikariCP(重点)等。

在Spring Boot 2.x中,对数据源的选择,采用了目前性能最佳的HikariCP。这个就是 Spring Boot2.x 中的默认数据源配置。


2 整合步骤

对于数据访问层,无论是SQL还是NOSQL,Spring Boot默认采用整合Spring Data的方式进行统一处理,添加大量自动配置,屏蔽了很多设置。
引入各种xxxTemplate,xxxRepository来简化我们对数据访问层的操作。
对我们来说只需要进行简单的设置即可。

版本:

SpringBoot(v2.3.2.RELEASE)

注意:

引入的组件,如果在父POM全局管理spring-boot依赖版本的前提下,只需要在项目pom文件的dependencies元素直接引入.

创建工程时,只需添加SQL模块的jdbc APIMySQL Driver即可,在配置文件中配置数据源信息,就可以获取到数据库的连接。

并且数据源默认使用的是HiKari。数据源信息是从DataSourceProperties获取的

2.1 引入JDBC的Starter

spring-boot-starter-jdbc

spring-boot-starter-jdbc提供了数据源配置、事务管理、数据访问等等功能,而对于不同类型的数据库,需要提供不同的驱动实现,才能更加简单地通过驱动实现根据连接URL、用户口令等属性直接连接数据库(或者说获取数据库的连接),因此对于不同类型的数据库,需要引入不同的驱动包依赖

pom.xml

<!--引入 SpringBoot 的 jdbc模块-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
  <exclusions>
      <!-- 如果是使用log4j2,则要排除SpringBoot 默认的logback 日志框架-->
      <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
  </exclusions>
</dependency>

a4ZbM8.png

spring-boot-starter-jdbc模块(间接引入spring-core、spring-beans、spring-tx)、spring-boot-starter和HikariCP三个依赖。

引入spring-boot-starter-jdbc模块后,SpringBoot是自动配置了jdbcTamplate的,可以使用jdbcTamplate来操作数据查询。

2.2 引入连接数据库的驱动

mysql-connector-java

对于MySQL而言,需要引入mysql-connector-java

其中有数据库抽象驱动接口java.sql.Driver的实现类:

  • 对于mysql-connector-java而言,常用的实现是com.mysql.cj.jdbc.Driver(MySQL8.x版本)。

pom.xml

<!--mysql8 驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2.3 配置数据源(默认HikariCP)

HikariCP官网

springboot2.x默认是用com.zaxxer.hikari.HikariDataSource作为数据源。
2.0以下默认采用的是org.apache.tomcat.jdbc.pool.DataSource作为数据源。

springboot2.x的spring-boot-starter-jdbc模块 默认使用HikariCP作为数据库的连接池

HikariCP,也就是Hikari Connection Pool,Hikari连接池。HikariCP的作者是日本人,而Hikari是日语,意义和light相近,也就是”光”。Simplicity is prerequisite for reliability(简单是可靠的先决条件)是HikariCP的设计理念,他是一款代码精悍的高性能连接池框架,被Spring项目选中作为内建默认连接池,值得信赖。

由于Spring Boot的自动化配置机制,大部分对于数据源的配置都可以通过配置参数的方式去改变。

在Spring Boot自动化配置中,对于数据源的配置可以分为两类:

  • 通用配置: 以spring.datasource.*的形式存在,主要是对一些即使使用不同数据源也都需要配置的一些常规内容。

    比如:数据库链接地址、用户名、密码等。通常就这些配置:

    # 注意MySQL8.x需要指定服务时区属性
    spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
    spring.datasource.username=xxx
    spring.datasource.password=xxx
    # 注意MySQL8.x的驱动
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
  • 数据源连接池配置:以spring.datasource.<数据源名称>.*的形式存在。

    比如:Hikari 的配置参数就是spring.datasource.hikari.*形式。

    注意,需要指明数据源的类型:

    spring.datasource.type: com.zaxxer.hikari.HikariDataSource
    

    下面这个是我们最常用的几个配置项及对应说明:

    # 连接池中允许的最大连接数。缺省值:10;推荐的公式:(cpu核心数*2+磁盘数)
    maximum-pool-size: 15
    # 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
    idle-timeout: 600000
    # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒
    connection-timeout: 30000
    # 连接只读数据库时配置为true, 保证安全
    read-only: false
    # 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';)
    max-lifetime: 1800000
    

2.3.1 数据源完整配置项表

name 描述 构造器默认值 默认配置validate之后的值 validate重置
autoCommit 自动提交从池中返回的连接 TRUE TRUE
connectionTimeout 等待来自池的连接的最大毫秒数 SECONDS.toMillis(30) = 30000 30000 如果小于250毫秒,则被重置回30秒
idleTimeout 连接允许在池中闲置的最长时间 MINUTES.toMillis(10) = 600000 600000 如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,则会被重置为0(代表永远不会退出);如果idleTimeout!=0且小于10秒,则会被重置为10秒
maxLifetime 池中连接最长生命周期 MINUTES.toMillis(30) = 1800000 1800000 如果不等于0且小于30秒则会被重置回30分钟
connectionTestQuery 如果您的驱动程序支持JDBC4,我们强烈建议您不要设置此属性 null null
minimumIdle 池中维护的最小空闲连接数 -1 10 minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize
maximumPoolSize 池中最大连接数,包括闲置和使用中的连接 -1 10 如果maxPoolSize小于1,则会被重置。当minIdle<=0被重置为DEFAULT_POOL_SIZE则为10;如果minIdle>0则重置为minIdle的值
metricRegistry 该属性允许您指定一个 Codahale / Dropwizard MetricRegistry 的实例,供池使用以记录各种指标 null null
healthCheckRegistry 该属性允许您指定池使用的Codahale / Dropwizard HealthCheckRegistry的实例来报告当前健康信息 null null
poolName 连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 null HikariPool-1
initializationFailTimeout 如果池无法成功初始化连接,则此属性控制池是否将 fail fast 1 1
isolateInternalQueries 是否在其自己的事务中隔离内部池查询,例如连接活动测试 FALSE FALSE
allowPoolSuspension 控制池是否可以通过JMX暂停和恢复 FALSE FALSE
readOnly 从池中获取的连接是否默认处于只读模式 FALSE FALSE
registerMbeans 是否注册JMX管理Bean(MBeans) FALSE FALSE
catalog 为支持 catalog 概念的数据库设置默认 catalog driver default null
connectionInitSql 该属性设置一个SQL语句,在将每个新连接创建后,将其添加到池中之前执行该语句。 null null
driverClassName HikariCP将尝试通过仅基于jdbcUrl的DriverManager解析驱动程序,但对于一些较旧的驱动程序,还必须指定driverClassName null null
transactionIsolation 控制从池返回的连接的默认事务隔离级别 null null
validationTimeout 连接将被测试活动的最大时间量 SECONDS.toMillis(5) = 5000 5000 如果小于250毫秒,则会被重置回5秒
leakDetectionThreshold 记录消息之前连接可能离开池的时间量,表示可能的连接泄漏 0 0 如果大于0且不是单元测试,则进一步判断:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),会被重置为0 . 即如果要生效则必须>0,而且不能小于2秒,而且当maxLifetime > 0时不能大于maxLifetime
dataSource 这个属性允许你直接设置数据源的实例被池包装,而不是让HikariCP通过反射来构造它 null null
schema 该属性为支持模式概念的数据库设置默认模式 driver default null
threadFactory 此属性允许您设置将用于创建池使用的所有线程的java.util.concurrent.ThreadFactory的实例 null null
scheduledExecutor 此属性允许您设置将用于各种内部计划任务的java.util.concurrent.ScheduledExecutorService实例 null null

2.3.2 使用其他数据源(Druid)

Druid(github)
Druid官网
Druid Spring Boot Starter
Druid的中文说明

要禁用默认的数据源,改成Durid提供的数据源, 引入Druid数据源需要额外添加依赖

Druid的最强大之处在于它有着强大的监控,可以监控我们发送到数据库的所有sql语句,
方便我们后期排插错误

当我们需要使用其他数据库连接池时:

  • 排除默认的数据源

  • 导入相关的jar包

  • 配置数据源信息

(1)排除默认数据源

<!--配置默认数据源 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <exclusions>
        <!-- 排除默认的 HikariCP 数据源 -->
        <exclusion>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </exclusion>
    </exclusions>
</dependency>

(2)引用 阿里巴巴的Druid数据源

<!-- 引用阿里巴巴的druid数据源 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.23</version>
</dependency>

(3)配置Druid数据源

Druid Spring Boot Starter 配置属性的名称完全遵照 Druid;

  • 我们可以通过 Spring Boot 配置文件(application.yml / application.properties)来配置Druid数据库连接池和监控,如果没有配置则使用默认值。具体配置可以参考Druid Spring Boot Starter

  • DruidDataSource 内提供 setter方法 的可配置属性都将被支持。你可以参考WIKI文档或通过IDE输入提示来进行配置。配置文件的格式你可以选择*.properties*.yml,效果是一样的,在配置较多的情况下推荐使用*.yml

  • 也可以自己写Druid的配置文件和@Configuration来配置;

这里我是自己定制的Druid的配置文件,在DruidDataSource里面 配置监控中心:

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@ConditionalOnMissingBean({DataSource.class})
@ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "com.alibaba.druid.pool.DruidDataSource"
)
@PropertySource(value = "classpath:druid.properties")
public class DruidConfig {

    /**
     * 注册 Druid 数据源
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

     /**
     * 配置 监控服务器
     * @return 返回监控注册的servlet对象
     */
    @Bean
    public ServletRegistrationBean statViewServlet() {
        // 
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        // 添加IP白名单
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        // 添加IP黑名单,当白名单和黑名单重复时,黑名单优先级更高
        servletRegistrationBean.addInitParameter("deny", "127.0.0.1");
        // 添加控制台管理用户
        servletRegistrationBean.addInitParameter("loginUsername", "123");
        servletRegistrationBean.addInitParameter("loginPassword", "123");
        // 是否能够重置数据
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    /**
     * 配置服务过滤器
     * @return 返回过滤器配置对象
     */
    @Bean
    public FilterRegistrationBean statFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        // 添加过滤规则
        filterRegistrationBean.addUrlPatterns("/*");
        // 忽略过滤格式
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*,");
        return filterRegistrationBean;
    }
}

配置完后我们启动SpringBoot程序访问:

http://localhost:8080/druid/ 就可以来到我们的登录页面,登录之后,就是我们上面添加的控制台,我们可以在上面很好的看到运行状况。


3 数据源的自动配置原理(以hikari为例)

3.1 单元测试及其结果

1. 单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootJdbcDemoApplicationTests {

    @Qualifier("dataSource")
    @Autowired
    private DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        Connection connection = null;
        try {
            // 获取连接
            connection = dataSource.getConnection();
            // 打印 数据源
            System.out.println("【dataSourceClass=】" + dataSource.getClass());
            // 打印 连接对象
            System.out.println("【connectionClass=】" + connection.getClass());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 断言,连接不可以为空,才关闭连接
            assert connection != null;
            connection.close();
        }
    }
}

2. 运行结果

14:49:28.092 [main] INFO  com.springboot.template.SpringbootJdbcDemoApplicationTests - Started SpringbootJdbcDemoApplicationTests in 1.818 seconds (JVM running for 3.051)

14:49:28.248 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
14:49:29.375 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.

// 单元测试的打印结果
【dataSourceClass=】class com.zaxxer.hikari.HikariDataSource
【connectionClass=】class com.zaxxer.hikari.pool.HikariProxyConnection

14:49:29.400 [SpringContextShutdownHook] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
14:49:29.407 [SpringContextShutdownHook] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
14:49:29.407 [SpringContextShutdownHook] INFO  org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'
Disconnected from the target VM, address: '127.0.0.1:57273', transport: 'socket'

可以看到, springboot2.x 默认数据源使用的是hikariDatasorece

3.2 原理解析

相关配置类,都可以在org.springframework.boot.autoconfigure.jdbc下找到

数据源的相关属性配置都是在 DataSourceProperties 中;
实际的数据源导入配置都在DataSourceConfiguration中;
实际的数据源的自动配置都在 DataSourceAutoConfiguration 中。

3.2.1 数据源的导入配置

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration

SpringBoot支持的数据源,有如下几个:

// SpringBoot默认支持的三个数据源
org.apache.commons.dbcp2.BasicDataSource: dbcp2的数据源
com.zaxxer.hikari.HikariDataSource: hikari的数据源(默认的)
org.apache.tomcat.jdbc.pool.DataSource: tomcat的数据源
// 使用其他的数据库连接池:例如Druid
Generic: 其他的数据源

3.2.1.1 hikari的数据源的配置原理

org.springframework.boot.jdbc.DataSourceConfiguration

根据项目导入的依赖和配置文件配置的限定,选择性的将SpringBoot支持的指定数据源,注册到Spring的IOC容器中。

1> 静态成员内部类 Hikari

abstract class DataSourceConfiguration {
    // 略
 
    // 配置类,以下几个注解同时成立,@Configuration才生效
    @Configuration(
        proxyBeanMethods = false
    )
    // 在当前类路径下必须存在 HikariDataSource.class, 配置类才被注册
    @ConditionalOnClass({HikariDataSource.class})
    // 仅在该注解规定的类DataSource不存在于 spring容器中时,配置类才被注册,避免生成多个dataSource
    @ConditionalOnMissingBean({DataSource.class})
    // 控制配置类是否应该注册,
    // 通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
    // 如果该值为空,则返回false;
    // 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
    // 如果返回值为false,则该configuration不生效;为true则生效。
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "com.zaxxer.hikari.HikariDataSource",
        matchIfMissing = true
    )
    static class Hikari {
        Hikari() {
        }

        @Bean
        @ConfigurationProperties(
            // 注入配置文件的属性配置,如初始连接数,最大最小等的前缀
            prefix = "spring.datasource.hikari"
        )
        HikariDataSource dataSource(DataSourceProperties properties) {
            // 创建 HikariDataSource ,绑定 相关属性信息 
            HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                // 设置连接池(数据源) 名称
                dataSource.setPoolName(properties.getName());
            }
            return dataSource;
        }
    }

    protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
        // 创建一个DataSourceBuilder对象,使用DataSourceBuilder创建数据源,利用反射创建指定type的数据源,并且绑定相关属性
        // 返回创建好的数据源
        return properties.initializeDataSourceBuilder().type(type).build();
    }
    
    //  略

}

2> HikariDataSource (Hikari 数据源)

org.springframework.boot.jdbc.HikariDataSource

实现了 HikariConfig 接口,这个就扣主要都是 Hikari 数据源的 配置信息;

继承 javax.sql.DataSource ,重写了 getConnection / setConnetion方法。

public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
 	
 	private final AtomicBoolean isShutdown = new AtomicBoolean();
    private final HikariPool fastPathPool;
    // Hikari 连接池,数据库连接由连接池提供
    private volatile HikariPool pool;
 	// setter、getter 等略
}

可以看出,Hikari数据源的配置信息主要从两个类中读取:

  • 一个是org.springframework.boot.autoconfigure.jdbc.DataSourceProperties

  • 另一个则是本类HikariDataSource的父类com.zaxxer.hikari.HikariConfig

3.2.1.2 数据源构建器(DataSourceBuilder)

org.springframework.boot.jdbc.DataSourceBuilder

上述使用到数据源构建器(DataSourceBuilder)类,创建 DataSource

其中的build方法,利用反射创建指定type的数据源,并且绑定相关属性,返回一个DataSource对象。

// 这里的 泛型T 就是 HikariDataSource
public final class DataSourceBuilder<T extends DataSource> {
    // 略
    // SpringBoot 支持的数据源 type
    private static final String[] DATA_SOURCE_TYPE_NAMES = 
                new String[]{"com.zaxxer.hikari.HikariDataSource", 
                "org.apache.tomcat.jdbc.pool.DataSource", 
                "org.apache.commons.dbcp2.BasicDataSource"};

    public T build() {
        // 获取 HikariDataSource 的Class 对象
        Class<? extends DataSource> type = this.getType();
        // 利用反射,获取指定类型的数据源
        DataSource result = (DataSource)BeanUtils.instantiateClass(type);
        // 设置连接数据库驱的动全类名,用于绑定到数据源
        this.maybeGetDriverClassName();
        // 绑定相关属性到 数据源
        // 相关属性就是 :
        // username、password、url、driverClassName
        this.bind(result);
        return result;
    }
    // 略
}

3.2.1.3 hikari数据源的自动配置

org.springframework.boot.jdbc.DataSourceAutoConfiguration

DataSourceAutoConfiguration是springboot加载数据源的核心配置类;

Spring Boot启动后会调用它,配置数据源。

DataSourceAutoConfiguration中有两个重要的嵌套类:

  • EmbeddedDatabaseConfiguration: 配置Spring Boot的嵌入示DataSource。除了Maven中加入相应的Driver,可以不做其他额外配置就能使用。从EmbeddedDatabaseType类可以看出,Spring Boot的内嵌DataSource支持 HSQL,H2,DERBY 这三种数据库。

  • PooledDataSourceConfiguration : 配置Spring Boot默认支持的一些DataSource。从org.springframework.boot.jdbc.DataSourceBuilder中可以看出,Spring Boot(2.x)只支持com.zaxxer.hikari.HikariDataSourceorg.apache.tomcat.jdbc.pool.DataSourceorg.apache.commons.dbcp2.BasicDataSource

以上两个配置类都会调用ConfigurationClassParser类的processConfigurationClass方法来处理

// 数据源的自动配置类
@Configuration(
    proxyBeanMethods = false
)
// 在当前类路径下存在DataSource.class,EmbeddedDatabaseType.class 时,这个配置类才注入到IOC容器
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
// 仅在该注解规定的类ConnectionFactory不存在于 spring容器中时,配置类才被注册
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
// 把 @ConfigurationProperties 注解的  DataSourceProperties类,注入到IOC容器
// 这就是自动配置的主要原因之一
@EnableConfigurationProperties({DataSourceProperties.class})
// 在运用的时候,获取该注解标识的类,注入到IOC容器
// DataSourcePoolMetadataProvidersConfiguration.class  自动装配每个数据源的元数据
// DataSourceInitializationConfiguration.class         数据源的初始化配置,
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
    // 略

    // 这里就使用到了上述的 DataSourceConfiguration抽象类,注入数据源(内部静态类)到IOC容器
    // 数据源导入的配置类
    @Configuration(
        proxyBeanMethods = false
    )
    // 配置有spring.datasource.type并且其值不是false 时生效,
    // 如果在当前类路径下存在: 
        // org.apache.tomcat.jdbc.pool.DataSource
        // com.zaxxer.hikari.HikariDataSource
        // org.apache.commons.dbcp.BasicDataSource
        // org.apache.commons.dbcp2.BasicDataSource
    // 任意一个时,这个配置类才可以注入到IOC容器
    @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
    // IOC容器中不存在DataSource,XADataSource类型的bean时,这个配置类才可以注入到IOC容器
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    // 在运用的时候,将DataSourceConfiguration配置类中,符合条件的数据源,注入到IOC容器
    // 就是根据条件限制,依次导入Tomcat,Hikari,Dbcp, Dbcp2的配置
    // 如果没有,就检查有没有 Generic.class 其他的数据源
    // 还没有,就是使用内嵌的数据源
    @Import({Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }

    // 略
}

3.2.1.4 使用其他数据源

org.springframework.boot.jdbc.DataSourceConfiguration

可以使用 spring.datasource.type 指定自定义的数据源类型,值要使用数据源全类名。

默认情况下,它是从类路径自动检测的,源码如下:

abstract class DataSourceConfiguration {
    // 略

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        DataSource dataSource(DataSourceProperties properties) {
            return properties.initializeDataSourceBuilder().build();
        }
    }

    // 略
}

可以看到,只要配置文件中,只配置了其他数据源,导入了其他数据源的依赖,那么,这个其他数据源就会被生成使用,转而会生成我们配置使用的数据源。

SpringBoot默认使用的数据源Hikari,因为不满足限定条件,而不会生成使用。

3.2.1.5 数据源的初始化

org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration

这个类,在DataSourceConfiguration中,被注入到IOC容器;

主要作用是,数据源的初始化配置。

@Configuration(
    proxyBeanMethods = false
)
// 运行时,注入这些标注的类
@Import({DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class})
class DataSourceInitializationConfiguration {
    // 略
    // 这个内部类
    // 主要是给 数据源bean,注册后置处理器
    // dataSourceInitializerPostProcessor
    static class Registrar implements ImportBeanDefinitionRegistrar {
        private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";

        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (!registry.containsBeanDefinition("dataSourceInitializerPostProcessor")) {
                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
                beanDefinition.setRole(2);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition("dataSourceInitializerPostProcessor", beanDefinition);
            }

        }
    }
}
DataSourceInitializerInvoke

org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoke

DataSourceInitializerInvoke,实现了ApplicationListener接口,实际上就是一个 监听器(ApplicationListener);

这里它监听的是 DataSourceSchemaCreatedEvent 事件,就是建表的事件。

作用:

  • 初始化的时候帮我们运行schene-文件(建表),和 data-(数据);

  • afterPropertiesSet方法,初始化bean的时候执行,可以针对某个具体的bean进行配置

class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean {
    // 略

    public void afterPropertiesSet() {
        // 从容器中拿到数据源对象初始化器
        DataSourceInitializer initializer = this.getDataSourceInitializer();
        if (initializer != null) {
            // createSchema()->runScripts(),运行建表语句
            boolean schemaCreated = this.dataSourceInitializer.createSchema();
            // 若建表语句创建成功
            if (schemaCreated) {
                // initialize()->initSchema(),运行插入数据的语句
                this.initialize(initializer);
            }
        }

    }

    // DataSourceSchemaCreatedEvent 发生时,触发此方法
    public void onApplicationEvent(DataSourceSchemaCreatedEvent event) {
        DataSourceInitializer initializer = this.getDataSourceInitializer();
        if (!this.initialized && initializer != null) {
            // 运行插入数据的语句
            initializer.initSchema();
            this.initialized = true;
        }

    }

    // 略
}
DataSourceInitializer

org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer

默认只需要在类路径下,将文件命名为:

# schema‐*.sql、data‐*.sql
# 默认规则:schema.sql,schema‐all.sql;
# 可以使用自定义位置:
schema:
    ‐ classpath:department.sql

就可以在SpringBoot启动后,执行sql脚本,在数据库,建表,更新数据。

部分源码:

class DataSourceInitializer {
    // 略
    
    // 建表
    boolean createSchema() {
        List<Resource> scripts = this.getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
        // 这里要在 application.properties 配置文件中配置,才会执行类路径下的sql脚本
        // spring.datasource.initialization-mode: always 
        if (!scripts.isEmpty()) {
            if (!this.isEnabled()) {
                logger.debug("Initialization disabled (not running DDL scripts)");
                return false;
            }

            String username = this.properties.getSchemaUsername();
            String password = this.properties.getSchemaPassword();
            this.runScripts(scripts, username, password);
        }

        return !scripts.isEmpty();
    }

    // 更新数据
    void initSchema() {
        List<Resource> scripts = this.getScripts("spring.datasource.data", this.properties.getData(), "data");
        if (!scripts.isEmpty()) {
            if (!this.isEnabled()) {
                logger.debug("Initialization disabled (not running data scripts)");
                return;
            }

            String username = this.properties.getDataUsername();
            String password = this.properties.getDataPassword();
            this.runScripts(scripts, username, password);
        }

    }

    // 获取 sql 脚本在类路径下的 位置
    // 默认是 classpath:schema-all.sql 和 classpath:schema.sql
    private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
        if (resources != null) {
            return this.getResources(propertyName, resources, true);
        } else {
            // platform 默认是 all
            String platform = this.properties.getPlatform();
            List<String> fallbackResources = new ArrayList();
            // fallback 默认是 schema
            // classpath:schema-all.sql
            fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
            // classpath:schema.sql
            fallbackResources.add("classpath*:" + fallback + ".sql");
            return this.getResources(propertyName, fallbackResources, false);
        }
    }

    // 略

}

看getScripts源码,它还会加载schema-${platform}.sql文件,或者data-${platform}.sql文件,其中platform就是spring.datasource.platform的值;当然,也可以自定义sql脚本的名称,不过,需要在类路径下,配置sql脚本的位置信息。

SpringBoot2.x 使用sql文件初始化的一些注意事项:

  • Springboot启动成功后,
    下次要将sql脚本内的建表语句注释,
    不然会报错:数据库表已经存在,无法创建
nested exception is java.sql.SQLSyntaxErrorException: Table 'admin' already exists
  • 在application配置文件加上spring.datasource.initialization-mode=always,否则不生效;
spring.datasource.initialization-mode  
初始化模式(springboot2.0),其中有三个值,
always : 为始终执行初始化,
embedded : 只初始化内存数据库(默认值),如h2等,
never : 为不执行初始化
  • sql脚本中尽量不要使用注释,否则会把紧跟的sql语句解释为注释,而不执行。

因为SpringBoot在启动时,只有检测到spring.datasource.initialization-mode=always配置;

然后,再检测spring.datasource.schema / spring.datasource.data,且配置的sql脚本语句不为空,才会去执行sql脚本。

application.yml

spring:
  datasource:
    platform: mysql # 数据库类型
    initialization-mode: always
    schema:
      - classpath:sql/user.sql
    data:
      - classpath:sql/userData.sql 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/69762.html

(0)
小半的头像小半

相关推荐

极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!