Springboot2.5.x版本之自动创建(H2/DERBY/HSQL)数据源源码分析-yellowcong

文章详细阐述了SpringBoot在没有配置特定数据库如MySQL、PostgreSQL时,如何通过自动配置使用H2、DERBY、HSQL等内嵌数据库创建数据源连接。主要涉及DataSourceAutoConfiguration类、DataSourceProperties的配置、EmbeddedDatabaseConnection的检测以及不同数据源连接池的创建,如Hikari。此外,还展示了如何通过配置文件设置数据库连接参数。

场景:当我们没有配置mysql,postgresql等数据源的时候,pom.xml里面引入了H2/DERBY/HSQL jar包,也没有配置连接,却有数据源创建的情况。
springboot启动的第一步
1.DataSourceAutoConfiguration 配置类启动
2.DataSourceProperties 配置类自动探测datasource的配置项
3.EmbeddedDatabaseConnection 做检测,自动生成数据库连接
4.DataSourceProperties.initializeDataSourceBuilder 创建数据源连接
5.DataSourceConfiguration 根据不同的数据源来创建连接池

1. 数据源配置启动类

找到 spring-boot-autoconfigure-2.5.x.jar 这个jar包,里面的META-INF/spring.factories 文件,有各个自动化配置的入口,数据库入口查找org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 这个类
在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,
...

2. DataSourceAutoConfiguration类核心代码

数据库自动数据库连接池配置的前提是,项目中没有别的模块创建了DataSource ( @ConditionalOnMissingBean({DataSource.class, XADataSource.class}) 这句代码就是做限制的)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.jdbc;


@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
//导入DataSourceProperties 的配置 ,实际数据库参数都是在这个类做的
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, InitializationSpecificCredentialsDataSourceInitializationConfiguration.class, SharedCredentialsDataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
    public DataSourceAutoConfiguration() {
    }

    //.... 省略代码

   //当没有手动创建DataSource的情况,就会走默认的数据源连接池
   //目前支持的数据源连接池
   //Hikari,Tomcat,Dbcp2,OracleUcp,Generic,DataSourceJmxConfiguration 
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DataSourceAutoConfiguration.EmbeddedDatabaseCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({EmbeddedDataSourceConfiguration.class})
    protected static class EmbeddedDatabaseConfiguration {
        protected EmbeddedDatabaseConfiguration() {
        }
    }
    //.... 省略代码
}

3. DataSourceProperties 数据源配置

注入配置的数据库连接池配置项,然后设定对应的EmbeddedDatabaseConnection ,其中initializeDataSourceBuilder的调用方是DataSourceConfiguration.createDataSource ,在最后创建数据库连接池的时候调用。

数据库探测规则:1.优先使用用户配置的(spring.datasource.url)url选项,2.如果有户没有配置,就会使用EmbeddedDatabaseConnection 来探测,默认会扫描jar(H2/DERBY/HSQL)包,取第一个为默认值。

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    //.... 省略代码
    //设置 EmbeddedDatabaseConnection的检查类
    public void afterPropertiesSet() throws Exception {
        if (this.embeddedDatabaseConnection == null) {
            this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
        }

    }

    // 创建连接池,调用了this.determineDriverClassName 探测数据库连接地址
    public DataSourceBuilder<?> initializeDataSourceBuilder() {
        return DataSourceBuilder.create(this.getClassLoader()).type(this.getType()).driverClassName(this.determineDriverClassName()).url(this.determineUrl()).username(this.determineUsername()).password(this.determinePassword());
    }

	//探测连接地址,默认会根据url进行分析探测,如果没有的情况,就会走EmbeddedDatabaseConnection 来获取内置数据库
    public String determineDriverClassName() {
        if (StringUtils.hasText(this.driverClassName)) {
            Assert.state(this.driverClassIsLoadable(), () -> {
                return "Cannot load driver class: " + this.driverClassName;
            });
            return this.driverClassName;
        } else {
            String driverClassName = null;
            if (StringUtils.hasText(this.url)) {
                driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
            }

            if (!StringUtils.hasText(driverClassName)) {
                driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
            }

            if (!StringUtils.hasText(driverClassName)) {
                throw new DataSourceProperties.DataSourceBeanCreationException("Failed to determine a suitable driver class", this, this.embeddedDatabaseConnection);
            } else {
                return driverClassName;
            }
        }
    }
    //.... 省略代码
}

4. EmbeddedDatabaseConnection 内置数据库探测

通过EmbeddedDatabaseConnection ,来探测数据库信息,会根据大家有没有(h2/derby/hsql)这些jar包,来确认是。

public enum EmbeddedDatabaseConnection {
    NONE((EmbeddedDatabaseType)null, (String)null, (String)null, (url) -> {
        return false;
    }),
    H2(EmbeddedDatabaseType.H2, DatabaseDriver.H2.getDriverClassName(), "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", (url) -> {
        return url.contains(":h2:mem");
    }),
    DERBY(EmbeddedDatabaseType.DERBY, DatabaseDriver.DERBY.getDriverClassName(), "jdbc:derby:memory:%s;create=true", (url) -> {
        return true;
    }),
    /** @deprecated */
    @Deprecated
    HSQL(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s", (url) -> {
        return url.contains(":hsqldb:mem:");
    }),
    HSQLDB(EmbeddedDatabaseType.HSQL, DatabaseDriver.HSQLDB.getDriverClassName(), "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s", (url) -> {
        return url.contains(":hsqldb:mem:");
    });
	
	//查找H2, HSQLDB, DERBY 中的driverClass 第一个
    private static EmbeddedDatabaseConnection getEmbeddedDatabaseConnection(String driverClass) {
        return (EmbeddedDatabaseConnection)Stream.of(H2, HSQLDB, DERBY).filter((connection) -> {
            return connection.isDriverCompatible(driverClass);
        }).findFirst().orElse(NONE);
    }
    //.... 省略代码
 }   

5. DataSourceConfiguration 数据库连接池配置类

里面包含了多种数据库连接池Hikari,Tomcat,Dbcp2,OracleUcp,Generic,DataSourceJmxConfiguration ,以Hikari举例 说明。

上面DataSourceAutoConfiguration 类中的PooledDataSourceConfiguration 配置内部类,有个注解@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class}) , 就会使用DataSourceConfiguration 中的数据库连接池配置信息。

abstract class DataSourceConfiguration {
    //.... 省略代码
    //创建数据库连接池的核心方法
    protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
        return properties.initializeDataSourceBuilder().type(type).build();
    }
    
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({HikariDataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @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 dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }

            return dataSource;
        }
    }
    //.... 省略代码    
}

附录

数据库测试连接配置

---
spring:
  profiles:
    active: db-h2
  #应用唯一标识
  application:
    name: BITSFLOW-CONSOLE
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      maximum-pool-size: 20
      connection-timeout: 300000
      max-life-time: 1800000
      idle-timeout: 0
      pool-name: "CONN-POOL"

---      
spring:
  h2:
    console:
      enabled: true
      path: /h2-console
      settings:
        trace: false
        web-allow-others: true
  config:
    activate:
      on-profile: db-h2
  datasource:
    url: jdbc:h2:mem:yc_test;MODE=MySql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
---
spring:
  config:
    activate:
      on-profile: db-postgresql
  datasource:
    url: jdbc:postgresql://127.0.0.1:5432/yc_test
    username: postgres
    password: demo
    driver-class-name: org.postgresql.Driver
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect

---
spring:
  config:
    activate:
      on-profile: db-mysql
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/yc_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&CreateDatabaseIfNotExists=true&allowMultiQueries=true
    username: root
    password: 'root'
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
在使用 Nacos 的启动命令时,如果遇到“Unknown parameter”错误提示,通常是由于某些参数未被正确识别或传递给 JVM。下面分析并解决这个问题。 --- ### 命令解析 你使用的完整命令如下: ```bash /app/nacos/bin/startup.sh -m standalone -Dderby.stream.error.file=/data/nacos/derby.log -Dnacos.logging.dir=/data/nacos/logs -Dnacos.data.dir=/data/nacos/work ``` #### 参数含义: 1. **`-m standalone`** 表示以单机模式运行 Nacos。 2. **`-Dderby.stream.error.file`** 设置 Derby 数据库的日志文件路径(主要用于嵌入式数据库场景)。 3. **`-Dnacos.logging.dir`** 指定 Nacos 的日志存放目录。 4. **`-Dnacos.data.dir`** 指定 Nacos 的工作数据目录(包括 Derby 数据库存储路径等)。 --- ### 报错原因分析 1. **脚本限制** `startup.sh` 是一个 Shell 脚本,它只接受特定的选项(如 `-m`),而像 `-D...` 这样的 JVM 参数通常不会直接作为脚本参数处理,而是需要通过额外的方式传入 JVM。 2. **JVM 参数未正确传递** `startup.sh` 默认只会将部分固定参数传递到 Java 启动命令中,如果你手动添加了其他 JVM 参数(例如 `-Dderby.stream.error.file` 等),可能会导致它们无法正常到达 JVM 层级。 --- ### 解决方法 #### 方法一:修改 `startup.sh` 打开 `/app/nacos/bin/startup.sh` 文件,找到类似以下的内容(这是构造 Java 启动命令的部分): ```sh java $JAVA_OPT $MODE -jar nacos-server.jar ``` 将其改为包含所有自定义 JVM 参数的形式,比如: ```sh JAVA_OPTS="-Dderby.stream.error.file=/data/nacos/derby.log \ -Dnacos.logging.dir=/data/nacos/logs \ -Dnacos.data.dir=/data/nacos/work" java $JAVA_OPTS $MODE -jar nacos-server.jar ``` 然后保存并重新运行 `startup.sh`。 --- #### 方法二:通过环境变量设置 有些版本允许通过环境变量覆盖默认的 JVM 参数。可以在运行前导出相关的环境变量,例如: ```bash export JAVA_OPTS="-Dderby.stream.error.file=/data/nacos/derby.log \ -Dnacos.logging.dir=/data/nacos/logs \ -Dnacos.data.dir=/data/nacos/work" /app/nacos/bin/startup.sh -m standalone ``` 这种方式无需改动脚本本身,更方便维护。 --- #### 方法三:修改配置文件 另一种替代方法是将这些路径硬编码进 Nacos 的配置文件中,而不是依赖于命令行参数。 编辑 `/app/nacos/conf/application.properties`,加入以下内容: ```properties # 自定义 Logs 存放路径 logging.path=/data/nacos/logs # 自定义 Derby 日志文件路径 derby.stream.error.file=/data/nacos/derby.log # 工作目录 nacos.core.datadir=/data/nacos/work ``` 完成后重启服务即可生效。 --- ### 总结 上述三种方式各有优劣: - 如果只是临时调整,则推荐直接修改环境变量; - 若要长期生效,建议改写 `application.properties` 配置文件; - 对于频繁变更需求较高的场合,可以考虑改造脚本逻辑以增强灵活性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂飙的yellowcong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值