Druid介绍

Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
同时Druid不仅仅是一个数据库连接池,它包括三个部分:

1.基于Filter-Chain模式的插件体系。
2.DruidDataSource 高效可管理的数据库连接池。
3.SQLParser

Druid可以做什么?

  1. 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
  2. 替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
  3. 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
  4. SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
    扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter-Chain机制,很方便编写JDBC层的扩展插件。

Springboot集成druid

Springboot集成Druid方案:
1、在POM中直接配置druid-spring-boot-starter,不需要添加监控的话不需要写额外代码。
2、配置druid,写几行代码,可以加入;

  • 环境介绍:
    MySQL5.7SpringBoot-2.5.3

集成步骤

只列出关键部分代码,详细看完整源码,文章末尾有链接。

  • 利用Spring Initializr创建一个springboot项目:druid_demo
  • pom添加druid数据源依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.14</version>
</dependency>
  • 配置Druid数据源属性文件
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false
      username: root
      password: root123
      # 下面为连接池的补充设置,应用到上面所有数据源中
      # 初始化大小,最小,最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # sql 校验
      validation-query: select count(1) from sys.objects Where type='U' And type_desc='USER_TABLE'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat # wall 若开启 wall,会把 if 中的 and 判断为注入进行拦截
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  • 添加Druid的配置类
@Configuration
public class DruidConfig {
    /**
     * 配置 Druid 监控管理后台的Servlet;
     * 内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
     * @return
     */
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 的父类
        // com.alibaba.druid.support.http.ResourceServlet 中找到
        Map<String,String> initParams = new HashMap<>();
        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        initParams.put("allow",""); //默认就是允许所有访问
        //deny:Druid 后台拒绝谁访问,表示禁止此ip访问
        // initParams.put("deny","192.168.10.132");
        bean.setInitParameters(initParams);
        return bean;
    }
    /**
     *  配置一个web监控的filter
     */
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String,String> initParams = new HashMap<>();
        initParams.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return  bean;
    }
}
  • 添加业务类

这是一个访问用户列表的接口:

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    UserService userService;

    @GetMapping("/list")
    public List<User>  list(){
        return userService.list(null);
    }
}
  • 启动,访问localhost:9000/user/list

iotdb session方式连接 连接池 druid连接池 springboot_数据库连接池

  • 访问localhost:9000/druid 进入druid数据源监控系统

iotdb session方式连接 连接池 druid连接池 springboot_数据库连接池_02

  • 查看SQL监控

实现密码加密

目前配置文件中配置的数据库用户名和密码都是明文的,这在某些情况下是不被允许的,那正好druid可以对其进行加密,按以下操作即可:

  • 利用工具类生成加密后密码和公钥
public static void main(String[] args) throws Exception {
        String password = "root123";
        System.out.println("明文密码: " + password);
        String[] keyPair = ConfigTools.genKeyPair(512);
        //私钥
        String privateKey = keyPair[0];
        //公钥
        String publicKey = keyPair[1];
        //用私钥加密后的密文
        password = ConfigTools.encrypt(privateKey, password);

        System.out.println("privateKey:" + privateKey);
        System.out.println("publicKey:" + publicKey);

        System.out.println("password:" + password);

        String decryptPassword = ConfigTools.decrypt(publicKey, password);
        System.out.println("解密后:" + decryptPassword);
    }

运行后得到:

iotdb session方式连接 连接池 druid连接池 springboot_druid加密_03

  • 修改配置文件

用上面得到的密码和publicKey替换配置文件中的密文:

iotdb session方式连接 连接池 druid连接池 springboot_数据库连接池_04

  • 修改DruidConfig配置类

添加以下自定义Druid数据源配置:

@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.type}")
private String type;
@Value("${spring.datasource.publicKey}")
private String publicKey;

@Value("${spring.datasource.druid.initial-size}")
private Integer initialSize;

@Value("${spring.datasource.druid.min-idle}")
private Integer minIdle;

@Value("${spring.datasource.druid.max-active}")
private Integer maxActive;

@Value("${spring.datasource.druid.max-wait}")
private Integer maxWait;

@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private Integer timeBetweenEvictionRunsMillis;

@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private Integer minEvictableIdleTimeMillis;

@Value("${spring.datasource.druid.validation-query}")
private String validationQuery;

@Value("${spring.datasource.druid.test-while-idle}")
private Boolean testWhileIdle;

@Value("${spring.datasource.druid.test-on-borrow}")
private Boolean testOnBorrow;

@Value("${spring.datasource.druid.test-on-return}")
private Boolean testOnReturn;

@Value("${spring.datasource.druid.pool-prepared-statements}")
private Boolean poolPreparedStatements;

@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private Integer maxPoolPreparedStatementPerConnectionSize;

@Value("${spring.datasource.druid.filters}")
private String filters;

@Value("${spring.datasource.druid.use-global-data-source-stat}")
private Boolean useGlobalDataSourceStat;

@Value("${spring.datasource.druid.connect-properties}")
private Properties connectProperties;

@Bean
@Primary
public DataSource druidDataSource() throws Exception {
    DruidDataSource datasource = new DruidDataSource();
    datasource.setUrl(url);
    datasource.setUsername(username);
    // 解密后,再 set 进对象
    datasource.setPassword(ConfigTools.decrypt(publicKey, password));
    logger.info("密码:" + ConfigTools.decrypt(publicKey, password));
    datasource.setInitialSize(initialSize);
    datasource.setMinIdle(minIdle);
    datasource.setMaxActive(maxActive);
    datasource.setMaxWait(maxWait);
    datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
    datasource.setValidationQuery(validationQuery);
    datasource.setTestWhileIdle(testWhileIdle);
    datasource.setTestOnBorrow(testOnBorrow);
    datasource.setTestOnReturn(testOnReturn);
    datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
    datasource.setConnectProperties(connectProperties);

    try {
        datasource.setFilters(filters);
    } catch (SQLException e) {
        logger.error("========druid configuration initialization filter========", e);
    }

    return datasource;
}
  • 启动项目,访问:localhost:9000/user/list测试正常返回数据即可。

注意:druid利用的反向非对称加密机制,也就是私钥加密,用公钥解密。

项目源码

https://gitee.com/indexman/druid_demo