【数据源】基于Druid来聊聊数据源

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 【数据源】基于Druid来聊聊数据源

1.概述

在Java编程环境中,javax.sql.DataSource 是Java标准API中的一个接口,用于提供数据库连接。DataSource对象允许应用程序以更高效的方式获取数据库连接,例如通过池化技术(Connection Pooling)来复用连接,从而减少频繁创建和关闭数据库连接所带来的开销。


市面上的数据源很多,本文将依托于目前市面上用的最多的,最主流的Druid数据源来聊一聊数据源技术。Druid是阿里巴巴开源的一个Java数据库连接池组件,它不仅提供高效、稳定和易于管理的数据库连接服务,而且集成了丰富的监控与诊断功能,特别适合在高并发环境下的生产级应用。以下是 Druid 数据源的主要特点:


高性能: Druid 通过连接池技术实现了对数据库连接的复用,极大地降低了建立新连接的开销,同时支持快速获取和释放连接。 它还具有强大的并发处理能力,通过优化算法减少了锁竞争,提高了多线程环境下的性能表现。


资源管理: Druid 提供了完善的连接管理机制,能够自动回收空闲连接,检测并关闭失效连接,并且支持配置连接的有效时长、最大连接数、最小连接数等参数以适应不同的负载场景。


监控与统计: 内置监控统计功能是 Druid 的一大亮点,它可以实时收集包括 SQL 执行次数、执行耗时、连接状态等在内的多种监控指标,并通过 Web 页面展示详细的监控信息和图表。 支持通过扩展插件系统来输出统计数据到各种监控系统中,例如 Log4j、Slf4j、Graphite 等。


SQL解析与拦截: Druid 可以解析 SQL 语句并进行优化,还能根据配置进行 SQL 拦截,实现诸如 SQL 注入防护、慢 SQL 监控等功能。


扩展性: Druid 设计了灵活的扩展接口,用户可以根据需求自定义连接池行为,比如添加额外的数据源代理逻辑、过滤器或监听器。


兼容性: Druid 兼容 JDBC 标准,支持广泛的数据库类型,包括 MySQL、Oracle、SQL Server、PostgreSQL 等。


零依赖: Druid 自身是一个轻量级的独立项目,没有其他外部依赖,可以方便地集成到任何基于 Java 的应用程序中,特别是在 Spring Boot 等框架下,可通过简单的配置即可启用 Druid 数据源。

2.使用

2.1.环境

直接用Spring Boot来演示了,方便快速点。

依赖:

千万注意要在spring boot环境中用要用专门的druid的starter!

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
        <!-- MyBatis Plus Starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- Alibaba Druid 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.20</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.6.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 

配置:

spring:
  application:
    name: testDemo
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01
    username: root
    password: admin
    # DruidDataSource额外配置
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
 

druid的常用参数:

# Druid 数据源配置(YAML格式)
druid:
  datasource:
    # 类型
    type: com.alibaba.druid.pool.DruidDataSource

    # 基本属性
    driverClassName: com.mysql.jdbc.Driver          # 根据实际情况设置数据库驱动类名
    url: jdbc:mysql://localhost:3306/mydatabase   # 数据库连接URL
    username: root                                # 数据库用户名
    password: password                            # 数据库密码

    # 连接池相关配置
    initialSize: 5                                 # 初始化时建立的连接数
    minIdle: 5                                    # 最小空闲连接数
    maxActive: 20                                 # 最大并发连接数
    maxWait: 60000                                # 获取连接等待超时时间(毫秒)
    timeBetweenEvictionRunsMillis: 60000          # 连接在池中最小生存的时间(毫秒),用于检查空闲连接是否有效
    validationQuery: SELECT 1 FROM DUAL           # 验证SQL语句,用来测试连接的有效性
    testWhileIdle: true                            # 是否在空闲时进行连接有效性检测
    testOnBorrow: false                           # 是否在借出连接时进行有效性检测
    testOnReturn: false                           # 是否在归还连接时进行有效性检测
    removeAbandoned: true                          # 是否自动回收被遗弃的连接
    removeAbandonedTimeout: 1800                  # 超过多长时间不活动则认为是被遗弃的连接(秒)

    # 日志与监控配置
    filters: stat,wall,slf4j                      # 启用过滤器,例如:统计信息过滤器(stat)、SQL防火墙过滤器(wall)和日志输出过滤器(slf4j)
    logSlowSql: true                               # 是否开启慢查询日志记录
    slowSqlMillis: 1000                           # 慢查询阈值(毫秒)
    maxPoolPreparedStatementPerConnectionSize: 20  # 每个连接上可以缓存的预编译Statement数量

    # 其他高级配置(根据实际需求选择启用或调整)
    connectionProperties: characterEncoding=UTF-8  # 设置连接属性,如字符集
 

2.2.连接复用

测试代码:

@SpringBootTest(classes = Main.class)
public class test {
    @Autowired
    DruidDataSource druidDataSource;
    @Test
    public void test01(){
        DruidPooledConnection connection01 = null;
        try {
            connection01 = druidDataSource.getConnection();
            System.out.println(connection01);
            connection01.close();
            DruidPooledConnection connection02 = druidDataSource.getConnection();
            System.out.println(connection02);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

测试结果:

可以看到close掉的连接会被收回然后复用。

2.3.监控

druid数据源支持对sql进行监控,可以监控慢sql、黑名单、白名单等。

配置:

package com.eryi.config;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DruidConfig {
    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        //先配置管理后台的servLet,访问的入口为/druid/
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
                new StatViewServlet(), "/druid/*");
        // IP白名单 (没有配置或者为空,则允许所有访问)
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        // IP黑名单 (存在共同时,deny优先于allow)
        servletRegistrationBean.addInitParameter("deny", "");
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "123");
        return servletRegistrationBean;
    }
    /**
     * @description 配置慢sql拦截器
     * @return
     */
    @Bean(name = "statFilter")
    public StatFilter statFilter(){
        StatFilter statFilter = new StatFilter();
        //慢sql时间设置,即执行时间大于200毫秒的都是慢sql
        statFilter.setSlowSqlMillis(30);
        statFilter.setLogSlowSql(true);
        statFilter.setMergeSql(true);
        return statFilter;
    }
    /**
     * @description 配置日志拦截器
     * @return
     */
    @Bean(name = "logFilter")
    public Slf4jLogFilter logFilter(){
        Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
        slf4jLogFilter.setDataSourceLogEnabled(true);
        slf4jLogFilter.setStatementExecutableSqlLogEnable(true);
        return slf4jLogFilter;
    }
}

慢sql监控:

黑白名单监控:

2.4.拦截器

过滤器是 Druid 数据源连接池的重要扩展点,通过一系列的 Filter 可以对数据库操作的各个阶段进行拦截和处理,实现诸如日志记录、性能统计、SQL 审计、权限控制、SQL 防注入等功能。


这种拦截和改写的能力可以实现很多功能,例如针对分库分表场景下的路由规则,或者在分布式事务中对事务边界内的SQL进行特殊处理。Sharding jdbc就是通过自定义数据源,然后在数据源中拦截sql,然后对sql进行改写和扩展从而实现的分库分表以及分布式事务。


Druid Filter 的工作原理与架构:


Filter接口与继承体系: com.alibaba.druid.filter.Filter 是所有过滤器需要实现的接口,定义了一系列针对数据库连接生命周期不同阶段的方法。 FilterAdapter 类是一个抽象适配器类,简化了 Filter 的实现过程,对于不关心的方法可以使用默认实现。


过滤器链与执行流程: Druid 在初始化时会根据配置构建一个过滤器链(FilterChain),每个请求(如获取连接、执行SQL等)都会按照顺序依次经过链上的每一个过滤器。过滤器之间通过代理模式相互协作,比如 DruidPooledConnection 本质上是对 JDBC Connection 的代理,而其内部又包含了多个 Statement 和 ResultSet 的代理对象,这些代理对象在创建时就会调用相应的过滤器方法。


主要的过滤器方法: 初始化与销毁:init(DruidDataSource dataSource) 用于初始化过滤器,destroy() 用于资源释放时清理。 连接相关:getConnection(DruidDataSource, DruidPooledConnection) 拦截获取连接的过程,close(DruidPooledConnection) 拦截关闭连接的操作。 SQL 执行相关:statementCreateAfter(StatementProxy, String) 在 Statement 创建后被调用,可在此处拦截并修改SQL;resultSetOpenAfter(ResultSetProxy, StatementProxy, String, boolean) 在 ResultSet 打开后被调用,可用于监控查询结果。 其他方法还包括对 PreparedStatement、CallableStatement 等的创建、关闭等事件的拦截。


启用过滤器: 通过 DruidDataSource 的 setFilters(String filters) 方法来指定要启用的过滤器集合,格式通常是逗号分隔的一系列过滤器别名或全限定类名。 自定义过滤器需注册到系统中,并且在配置文件中通过别名引用,也可以利用 SPI(服务提供者接口)机制自动发现和加载。


内置过滤器示例: stat:统计信息过滤器,收集数据库连接相关的各种统计指标。 wall:SQL防火墙过滤器,用于防止SQL注入攻击,对SQL语句进行安全检查。 config:配置信息过滤器,从配置文件读取特定的参数配置数据。

内置过滤器的注册使用:

import com.alibaba.druid.pool.DruidDataSource;
@Configuration
public class DruidConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        // 设置基础数据库连接属性(如url、username、password)
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        // 启用并配置内置过滤器
        dataSource.setFilters("stat,wall");  // 这里以StatFilter(统计信息过滤器)和WallFilter(SQL防火墙过滤器)为例
        // 对于StatFilter可能需要进一步配置统计功能(可选)
        StatFilter statFilter = new StatFilter();
        statFilter.setLogSlowSql(true);  // 开启慢查询日志
        statFilter.setMergeSql(true);   // 合并SQL语句(去除参数)
        // 注册到Druid数据源
        dataSource.getProxyFilters().add(statFilter);
        // WallFilter通常只需要开启即可,其默认提供了SQL注入防护等功能
        // 若有特殊配置需求,请参考官方文档进行设置
        return dataSource;
    }
}
  • StatFilter:用于收集数据库连接池及SQL执行的统计信息,包括连接数、执行次数、耗时等。 可以通过配置来决定是否记录慢查询、合并SQL等操作。
  • WallFilter: 提供SQL防火墙功能,可以防止SQL注入攻击,并对SQL执行进行一些安全策略控制。 配置项丰富,例如允许设置SQL黑名单、白名单,限制SQL长度,禁止全表更新等

自定义过滤器的注册使用:

自定义Druid过滤器用于拦截SQL时,通常需要继承com.alibaba.druid.filter.FilterAdapter或其子类,并重写与SQL执行相关的回调方法。以下是一个基础的示例,展示如何创建一个简单的自定义过滤器来拦截和修改PreparedStatement中的SQL:

import com.alibaba.druid.filter.FilterAdapter;
import com.alibaba.druid.proxy.jdbc.ConnectionProxy;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxy;
import com.alibaba.druid.sql.parser.SQLParserUtils;
import com.alibaba.druid.sql.parser.SQLStatement;
import com.alibaba.druid.util.JdbcConstants;
@Component
public class CustomSqlInterceptorFilter extends FilterAdapter {
    @Override
    public PreparedStatementProxy connection_prepareStatement(ConnectionProxy connection, String sql) throws SQLException {
        // 1. 解析SQL语句
        SQLStatement statement = SQLParserUtils.parse(sql, JdbcConstants.MYSQL, false);
        // 2. 在这里可以检查、记录或修改SQL语句
        if (shouldModifySql(sql)) {
            // 假设我们有一个判断逻辑,决定是否要修改SQL
            String modifiedSql = modifySql(sql);
            return connection.prepareStatement(modifiedSql);
        }
        // 3. 如果不需要修改,则直接返回原始的PreparedStatement代理对象
        return connection.prepareStatement(sql);
    }
    private boolean shouldModifySql(String sql) {
        // 根据你的业务逻辑判断是否需要修改SQL语句
        // 这里仅作为示例,实际逻辑请自行实现
        return true; // 示例:始终返回true表示总是修改SQL
    }
    private String modifySql(String originalSql) {
        // 修改SQL的具体逻辑,例如添加额外的查询条件
        return originalSql + " AND your_condition = ?";
    }
    // 其他可能需要重写的方法...
}
@Configuration
public class DruidConfig {
    @Autowired
    private CustomSqlInterceptorFilter customSqlInterceptorFilter;
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        // 设置其他基本属性...
        // 注册自定义过滤器,这里假设你的过滤器已经在Spring容器中被实例化和管理
        dataSource.getProxyFilters().add(customSqlInterceptorFilter);
        // 或者通过别名引用(需要先进行别名注册)
        // dataSource.setFilters("stat,wall,customSqlInterceptor"); 
        return dataSource;
    }
}

3.tomcat中的数据源

Tomcat 自身提供了一个内置的数据源实现,它基于Apache Commons DBCP(DataBase Connection Pool)或者其它连接池库(例如,从Tomcat 8.5开始,默认使用了HikariCP)。这意味着在Tomcat服务器中,你可以配置一个JDBC数据源,并将其作为全局资源注册到JNDI(Java Naming and Directory Interface)命名空间中。应用程序可以通过JNDI查找和使用这些预配置的数据源,从而方便地进行数据库连接管理和复用。 在实际配置时,你需要在Tomcat的server.xml或特定应用的context.xml文件中添加相应的数据源配置信息,包括数据源名称、数据库驱动类、URL、用户名和密码等属性。这样,在部署的应用程序就可以通过JNDI查找并获取这个数据源,而无需在应用程序代码中硬编码数据库连接信息,提高了代码的可移植性和管理性。


这部分内容有点深了,大后期博主会专门开一个系列来聊一聊JAVA规范以及手写一个tomcat和大家一起从一个完整的角度来看整个JAVA EE体系,敬请期待。

目录
相关文章
|
1月前
SpringMVC+Mybatis两个数据源实现(一)
SpringMVC+Mybatis两个数据源实现(一)
|
1月前
SpringMVC+Mybatis两个数据源实现(二)
SpringMVC+Mybatis两个数据源实现(二)
|
7月前
|
Java 数据库连接 应用服务中间件
原生JDBC使用C3p0数据源和dbcp数据源
原生JDBC使用C3p0数据源和dbcp数据源
74 0
|
Oracle Java 关系型数据库
Spring+Mybatis多数据源配置(二)——databaseIdProvider的使用
 在上一篇同系列的博文中,讲到配置多数据源,然后根据config.properties配置不同的数据库,进行切换。而且需要根据不同的数据库,配置不同的mybatis sql映射配置文件,如下: classpath...
2237 0
|
8月前
|
druid 关系型数据库 MySQL
数据源管理(baomidou和druid)
数据源管理(baomidou和druid)
|
SQL 监控 druid
使用Druid数据源并查看监控页面
使用Druid数据源并查看监控页面
1250 0
|
监控 druid 数据库
整合Druid数据源
整合Druid数据源
|
监控 druid 前端开发
整合基本 JDBC 与数据源|学习笔记
快速学习整合基本 JDBC 与数据源
91 0
|
消息中间件 存储 SQL
Flink 数据源 DataSource是这个样子的?(三)
因为本篇文章中,有个 Kafka 数据源的 Demo,在一开始解答小伙伴有可能的困惑:
Flink 数据源 DataSource是这个样子的?(三)
|
消息中间件 存储 缓存
Flink 数据源 DataSource是这个样子的?(二)
因为本篇文章中,有个 Kafka 数据源的 Demo,在一开始解答小伙伴有可能的困惑:
Flink 数据源 DataSource是这个样子的?(二)