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

川长思鸟来 2024-04-25 20:21 151阅读 0赞

目录

1.概述

2.使用

2.1.环境

2.2.连接复用

2.3.监控

2.4.拦截器

3.tomcat中的数据源


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!

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-test</artifactId>
  9. </dependency>
  10. <!-- MySQL驱动 -->
  11. <dependency>
  12. <groupId>mysql</groupId>
  13. <artifactId>mysql-connector-java</artifactId>
  14. <version>8.0.29</version>
  15. </dependency>
  16. <!-- MyBatis Plus Starter -->
  17. <dependency>
  18. <groupId>com.baomidou</groupId>
  19. <artifactId>mybatis-plus-boot-starter</artifactId>
  20. <version>3.5.1</version>
  21. </dependency>
  22. <!-- Alibaba Druid 数据源 -->
  23. <dependency>
  24. <groupId>com.alibaba</groupId>
  25. <artifactId>druid-spring-boot-starter</artifactId>
  26. <version>1.2.20</version>
  27. </dependency>
  28. </dependencies>
  29. <dependencyManagement>
  30. <dependencies>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-dependencies</artifactId>
  34. <version>2.6.3</version>
  35. <type>pom</type>
  36. <scope>import</scope>
  37. </dependency>
  38. </dependencies>
  39. </dependencyManagement>

配置:

  1. spring:
  2. application:
  3. name: testDemo
  4. datasource:
  5. type: com.alibaba.druid.pool.DruidDataSource
  6. driver-class-name: com.mysql.cj.jdbc.Driver
  7. url: jdbc:mysql://localhost:3306/db01
  8. username: root
  9. password: admin
  10. # DruidDataSource额外配置
  11. druid:
  12. initial-size: 5
  13. min-idle: 5
  14. max-active: 20
  15. max-wait: 60000
  16. time-between-eviction-runs-millis: 60000
  17. min-evictable-idle-time-millis: 300000
  18. validation-query: SELECT 1 FROM DUAL
  19. test-while-idle: true
  20. test-on-borrow: false
  21. test-on-return: false
  22. pool-prepared-statements: true
  23. 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.连接复用

测试代码:

  1. @SpringBootTest(classes = Main.class)
  2. public class test {
  3. @Autowired
  4. DruidDataSource druidDataSource;
  5. @Test
  6. public void test01(){
  7. DruidPooledConnection connection01 = null;
  8. try {
  9. connection01 = druidDataSource.getConnection();
  10. System.out.println(connection01);
  11. connection01.close();
  12. DruidPooledConnection connection02 = druidDataSource.getConnection();
  13. System.out.println(connection02);
  14. } catch (SQLException e) {
  15. throw new RuntimeException(e);
  16. }
  17. }
  18. }

测试结果:

6080dfe4a04a42a29bcd1ee506acd5b2.png

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

2.3.监控

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

配置:

  1. package com.eryi.config;
  2. import com.alibaba.druid.filter.logging.Slf4jLogFilter;
  3. import com.alibaba.druid.filter.stat.StatFilter;
  4. import com.alibaba.druid.support.http.StatViewServlet;
  5. import org.springframework.boot.web.servlet.ServletRegistrationBean;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. @Configuration
  9. public class DruidConfig {
  10. @Bean
  11. public ServletRegistrationBean<StatViewServlet> statViewServlet() {
  12. //先配置管理后台的servLet,访问的入口为/druid/
  13. ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
  14. new StatViewServlet(), "/druid/*");
  15. // IP白名单 (没有配置或者为空,则允许所有访问)
  16. servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
  17. // IP黑名单 (存在共同时,deny优先于allow)
  18. servletRegistrationBean.addInitParameter("deny", "");
  19. servletRegistrationBean.addInitParameter("loginUsername", "admin");
  20. servletRegistrationBean.addInitParameter("loginPassword", "123");
  21. return servletRegistrationBean;
  22. }
  23. /**
  24. * @description 配置慢sql拦截器
  25. * @return
  26. */
  27. @Bean(name = "statFilter")
  28. public StatFilter statFilter(){
  29. StatFilter statFilter = new StatFilter();
  30. //慢sql时间设置,即执行时间大于200毫秒的都是慢sql
  31. statFilter.setSlowSqlMillis(30);
  32. statFilter.setLogSlowSql(true);
  33. statFilter.setMergeSql(true);
  34. return statFilter;
  35. }
  36. /**
  37. * @description 配置日志拦截器
  38. * @return
  39. */
  40. @Bean(name = "logFilter")
  41. public Slf4jLogFilter logFilter(){
  42. Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
  43. slf4jLogFilter.setDataSourceLogEnabled(true);
  44. slf4jLogFilter.setStatementExecutableSqlLogEnable(true);
  45. return slf4jLogFilter;
  46. }
  47. }

慢sql监控:

5450ed6274b94f34959825069b38456c.png

黑白名单监控:

5c1b8cc2692f401990603d9dda7c3224.png

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:配置信息过滤器,从配置文件读取特定的参数配置数据。

内置过滤器的注册使用:

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

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

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

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

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体系,敬请期待。

发表评论

表情:
评论列表 (有 0 条评论,151人围观)

还没有评论,来说两句吧...

相关阅读