首页 > 其他分享 >【SpringBoot】【一】初识数据源连接池

【SpringBoot】【一】初识数据源连接池

时间:2024-04-21 21:12:33浏览次数:29  
标签:SpringBoot 数据源 private class 创建 连接 连接池

1  前言

上节我们看了看,SpringBoot 启动后都有哪些线程,看到有一部分是关于数据源连接池的,那么这节我们就看看数据源连接池都是如何工作的。

我们本节就从这两个问题看起:

(1)连接池是如何创建的,也就是什么时候创建的呢?

(2)连接是什么时候放进连接池的?是创建完就初始化了一批新的连接,还是等获取的时候才开始创建?或者说获取连接的过程是什么?

2  实践

2.1  连接池的创建时机

老生常谈,SpringBoot 直接自动装配找起:

进到 DataSourceAutoConfiguration 看看,有两个关键的要素:

(1)DataSourceInitializationConfiguration 数据源初始化配置类(这个就是创建连接池的入口)

(2)5种类型的数据源配置:hikari、tomcat、dbcp、generic、jmx

看到这里,我们思考一个问题,我们可以看到会引入 5种类型的数据源,那么为什么 SpringBoot 默认的数据源是 Hikari 的呢,怎么做到的呢?

这个就看到这。

我们接着看 DataSourceInitializationConfiguration :

@Configuration(proxyBeanMethods = false)
// 引入了两个类 一个 processor 用于引入后边的 invoker 一个 invoker 就是用来初始化连接池的
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
class DataSourceInitializationConfiguration {
    /**
     * {@link ImportBeanDefinitionRegistrar} to register the
     * {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
     * issues.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar {
        private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            // 注入 Processor
            if (!registry.containsBeanDefinition(BEAN_NAME)) {
                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
                beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                // We don't need this one to be post processed otherwise it can cause a
                // cascade of bean instantiation that we would rather avoid.
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
            }
        }
    }
}

那我们继续看看 DataSourceInitializerPostProcessor:

/**
 * {@link BeanPostProcessor} used to ensure that {@link DataSourceInitializer} is
 * initialized as soon as a {@link DataSource} is.
 * Bean 的后置处理器 也就是在 Bean的生命周期最后一步初始化里调用到 后置处理器
 * @author Dave Syer
 */
class DataSourceInitializerPostProcessor implements BeanPostProcessor, Ordered {
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
    @Autowired
    private BeanFactory beanFactory;
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 如果当前 Bean 是数据源类型的就进行获取,也就是进行创建
        if (bean instanceof DataSource) {
            // force initialization of this bean as soon as we see a DataSource
            this.beanFactory.getBean(DataSourceInitializerInvoker.class);
        }
        return bean;
    }
}

那看到这里,我们上边的默认数据源是 Hikari,那么就是在创建这个数据源的时候,进入到这里的 Processor的,是不是这样呢?我们 debug看看,确实如此哈。

那我们继续看看 DataSourceInitializerInvoker:

package org.springframework.boot.autoconfigure.jdbc;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.log.LogMessage;

/**
 * Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
 * {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on
 * a {@link DataSourceSchemaCreatedEvent}.
 * 监听事件以及实现了 InitializingBean初始化 Bean 
 * @author Stephane Nicoll
 * @see DataSourceAutoConfiguration
 */
class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean {

    private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class);

    private final ObjectProvider<DataSource> dataSource;

    private final DataSourceProperties properties;

    private final ApplicationContext applicationContext;

    private DataSourceInitializer dataSourceInitializer;

    private boolean initialized;

    DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource, DataSourceProperties properties,
            ApplicationContext applicationContext) {
        this.dataSource = dataSource;
        this.properties = properties;
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        DataSourceInitializer initializer = getDataSourceInitializer();
        if (initializer != null) {
            boolean schemaCreated = this.dataSourceInitializer.createSchema();
            if (schemaCreated) {
                initialize(initializer);
            }
        }
    }

    private void initialize(DataSourceInitializer initializer) {
        try {
            this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer.getDataSource()));
            // The listener might not be registered yet, so don't rely on it.
            if (!this.initialized) {
                this.dataSourceInitializer.initSchema();
                this.initialized = true;
            }
        }
        catch (IllegalStateException ex) {
            logger.warn(LogMessage.format("Could not send event to complete DataSource initialization (%s)",
                    ex.getMessage()));
        }
    }

    @Override
    public void onApplicationEvent(DataSourceSchemaCreatedEvent event) {
        // NOTE the event can happen more than once and
        // the event datasource is not used here
        DataSourceInitializer initializer = getDataSourceInitializer();
        if (!this.initialized && initializer != null) {
            initializer.initSchema();
            this.initialized = true;
        }
    }

    private DataSourceInitializer getDataSourceInitializer() {
        if (this.dataSourceInitializer == null) {
            DataSource ds = this.dataSource.getIfUnique();
            if (ds != null) {
                this.dataSourceInitializer = new DataSourceInitializer(ds, this.properties, this.applicationContext);
            }
        }
        return this.dataSourceInitializer;
    }

}

我们可以看到该类(1)监听 DataSourceSchemaCreatedEvent 事件 (2)实现了 InitializingBean,那我们刚才在 Processor 里看到的 getBean 也只是创建出来 DataSourceInitializerInvoker 这个对象,并没有进行实际的连接池的创建操作。

莫非是在监听到事件或者执行 InitializingBean 的时候创建的?其实不是,但也不能说绝对不是,因为我发现连接池的创建是在你第一次获取连接的时候,创建出来的。DataSourceInitializerInvoker 能帮我们执行一些数据库的脚本,当你有脚本执行的话,他就会获取连接了,也就会初始化连接池了哈。

我是如何看到连接池是在第一次获取连接的时候,创建的呢?这里我是通过看日志以及 debug 看的。

看启动日志,大家心细的会发现,数据源启动的时候,会有几行日志:

那么谁第一次获取连接的呢?我看线程的方法链路看又是 SpringBoot 的监控,又是 actuator 那么监控包,看 jdbc 连接的健康情况过来的,这玩意看起来还挺重要。

好,那我们第一个问题看下来,首先是自动装配,引入我们的数据源,并且注入了数据源的一个 Processor ,可以用于数据库脚本的启动执行。而数据库连接池的创建是落在第一次获取连接的时候,进行创建的,接下来,我们就看一下连接的获取过程,顺便就把连接池的创建也看了哈。

2.2  连接池的创建

不知道大家看过我之前的数据源的连接协同以及在动态数据源下的连接协同,其实连接的获取都是从数据源获取的,那我们这里就直接从 HikariDataSource 的 getConnection 看起:

/** 获取连接 */
@Override
public Connection getConnection() throws SQLException{
   // 已经关闭的话 直接报异常
   if (isClosed()) {
      throw new SQLException("HikariDataSource " + this + " has been closed.");
   }
   // HikariDataSource 的数据源的带参的构造方法中会直接创建连接池并赋值给 fastPathPool 和 pool
   if (fastPathPool != null) {
      return fastPathPool.getConnection();
   }
   // 双重检查
   HikariPool result = pool;
   if (result == null) {
      synchronized (this) {
         result = pool;
         if (result == null) {
            // 配置以及参数校验
            validate();
            // 获取连接池名字 并打印日志
            LOGGER.info("{} - Starting...", getPoolName());
            try {
               // 创建连接池
               pool = result = new HikariPool(this);
               // 标记 seal 表示已经创建过,防止多次创建
               this.seal();
            }
            catch (PoolInitializationException pie) {
               if (pie.getCause() instanceof SQLException) {
                  throw (SQLException) pie.getCause();
               }
               else {
                  throw pie;
               }
            }
            // 打印日志
            LOGGER.info("{} - Start completed.", getPoolName());
         }
      }
   }
   // 从连接池中获取连接
   return result.getConnection();
}

可以看到大概分三步:

(1)配置以及参数的校验

(2)创建连接池

(3)获取连接

3  小结

好啦,本节就看到这里,关于参数的校验以及连接池的创建,我发现细节还挺多,本节就暂时写到这里,内容太多容易造成疲惫,我们下节再来看这两点哈。本节我们先对连接池的创建时机以及创建过程,有个大概的了解,下节我们继续,有理解错误的地方还请欢迎指正哈。

标签:SpringBoot,数据源,private,class,创建,连接,连接池
From: https://www.cnblogs.com/kukuxjx/p/18149340

相关文章

  • dist资源包放入springboot运行遇到的问题
    1、在vue、react使用npmrunbuild打包将dist包放入resources下2、通过浏览器访问本地在访问路径会出现404,打开dist包下的index.html发现打包后的指向样式不对,更改指向后,发现还是404把拦截器修改为排除所有路径后,页面不再404,说明资源没有显示。修改资源静态映射指向dis......
  • 短链接口设计&禁用Springboot执行器端点/env的安全性
    短链接口设计//短链接服务跳转方式,实现短链接转长链接的请求。@GetMapping("/{code}")publicStringredirectUrl(@PathVariable("code")Stringcode){return"redirect:"+shortUrl.getLongUrl();}禁用Springboot执行器端点/env的安全性#关闭健康检查不安全接口end......
  • 基于SpringBoot+Vue毕业生信息招聘平台系统
    需求分析3.1技术可行性:技术背景毕业生信息招聘平台是在Windows操作系统中进行开发运用的,而且目前PC机的各项性能已经可以胜任普通网站的web服务器。系统开发所使用的技术也都是自身所具有的,也是当下广泛应用的技术之一。系统的开发环境和配置都是可以自行安装的,系统使用Java开......
  • 计算机毕业设计源码-基于 SpringBoot 开发的班级综合测评系统研究与实现
    技术栈开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9浏览器:谷歌浏览器3.需求分析用户需求分析根据账号登陆进入班级综合测评管理系统,系统根据角色展示相应......
  • springboot启动原理
     启动类上的注解,会扫描路径下的类进容器进行实例化。这样访问时springmvc的dispa就可以访问到这个类了。newDispatcherServlet(webapplication)springmvc需要一个web容器。这个容器参数,在startTomcat(applicationContext)方法里面传入。 ......
  • 【Java 线程】SpringBoot 启动后都有哪些线程呢?
    1 前言现在流行搞微服务,基本也都是SpringBoot打底的,那么你可否知道一个基本的SpringBoot启动后,都开辟了哪些线程呢?这节我们就来看看。为什么要看呢?这个主要是增加对服务的了解,比如你管的支付中心或者订单中心,你都有哪些线程,各个线程都是干什么的,你不了解这些你怎么调优,你......
  • springboot java调用flask python写的
    服务a用flask,服务b用的springboot,服务a写的接口,用python很容易就调通了,java来调,坑有点多1、url最后的斜杠必须两边对应上,否则flask会先308,而且contenttype[text/html;charset=utf-8],连对应的HttpMessageConverter都没有org.springframework.web.client.RestClientException:......
  • ETLCloud中数据源使用和管理的技巧
    ETL中数据源管理的重要性在现代企业信息化进程中,数据已成为驱动决策、优化运营、提升竞争力的关键要素。而作为数据处理与分析的重要环节,ETL(Extract, Transform, Load)过程承担着从多种异构数据源中抽取数据,进行必要的转换,并将其加载到目标系统(如数据仓库或数据湖)中的重任。其中......
  • SpringBoot 上传图片
    1概述新做的博客系统需要在markdown文本中插入图片,之前完成过上传图片的相关配置,但未做总结,借着这个机会,对于springboot上传图片接口的相关配置和操作,做一个系统性阐述。以作为未来相关业务的参考。本文主要阐述后端相关配置,少量前端(vue3)内容仅是为了作为测试。2配置文......
  • 基于springboot的图书个性化推荐系统
     介绍图书个性化推荐系统的主要使用者分为管理员和学生,实现功能包括管理员:首页、个人中心、学生管理、图书分类管理、图书信息管理、图书预约管理、退换图书管理、管理员管理、留言板管理、系统管理,学生:首页、个人中心、图书预约管理、退换图书管理、我的收藏管理,前台首页;首页......