SpringBoot+Druid你还只是用做数据库连接池吗?还可以这么用用

Hi,大家好,我是抢老婆酸奶的小肥仔。

最近好久都没更新了,因为自己在折腾一个系统,由于不熟悉前端,花了很多时间搞这个,等搞完了到时候分享给大家瞄瞄。

Druid相信大家再熟悉不过了,它是由阿里团队开发的,不仅提供了数据库连接池,同时也提供了强大的监控和扩展功能,今天来聊聊这个我们经常用到的Druid。

废话不多说,直接开撸。

项目依赖(以版本1.2.16为例):

Maven

XML 复制代码
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.16</version>
</dependency>

Gradle:

GraphQL 复制代码
implementation group: 'com.alibaba', name: 'druid-spring-boot-starter', version: '1.2.16'

1、连接池使用

既然提到了Druid数据库连接池,那我们先来说说Druid数据库连接池的应用,如果对这一块比较熟悉的小伙伴可以自行跳过。

1.1 数据库配置

在配置文件application.properties或application.yml中配置数据库信息,以application.yml为例。

YAML 复制代码
#数据库连接
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3308/test?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 15
      min-idle: 15
      max-active: 200
      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: false
      connection-properties: false

在配置数据库信息时,指定type为Druid数据库连接池,当然也可以不配置。

常用配置参数说明:

参数 说明
initial-size 初始化时建立物理连接的个数
min-idle 最小连接池数量
max-active 最大连接池数据量
max-wait 获取连接时最大等待时间,单位:ms
time-between-eviction-runs-millis 设置间隔多久才进行一次检测,检测需要关闭空闲连接,单位:ms
min-evictable-idle-time-millis 设置一个连接在连接池中的最小生存时间,单位:ms
validation-query 用来检测连接是否有效SQL,设置为null时,test-while-idle,test-on-borrow, test-on-return不起作用
test-on-borrow 申请连接时,执行validation-query设置的SQL检测连接是否有效,会降低性能
test-on-return 归还连接时,执行validation-query设置的SQL检测连接是否有效,会降低性能
test-while-idle 申请连接时,当空闲时间大于time-between-eviction-runs-millis设置的时间时,执行validation-query设置的SQL检测连接是否有效。建议设置为true,不影响性能,并保证安全性
pool-prepared-statements 是否启用prepared-statements缓存,即PsCache,对支持游标的数据库性能提升巨大,如:Oracle;Mysql则建议关闭
connection-properties 打开mergeSql功能,记录慢SQL

1.2 设置Druid配置

设置完成配置文件的配置后,我们来设置Druid配置。即创建Druid的配置类。

JAVA 复制代码
/**
 * @author: jiangjs
 * @description: druid连接池配置
 * @date: 2023/10/20 14:55
 **/
@Configuration
public class DruidDataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource baseDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
}

@ConfigurationProperties:获取配置文件的配置属性信息。prefix:表示以某字符下的属性信息,遵循驼峰命名规则。

完成上面的操作后,就可以愉快的使用druid了。

2、监控

在文章开始,就提到过Druid提供了一系列的监控。例如SQL监控,Spring监控等。下面我们来说说这些监控中比较重要几个监控。

2.1 开启监控

要想使用Druid的监控功能,需要做一些操作配置。即设置ServletRegistrationBean,FilterRegistrationBean。

ServletRegistrationBean:是一个用来报装自定义Servlet的Spring Bean,通过Spring容器来管理Servlet,并可以设置一些属性,如初始化参数等。我们通过ServletRegistrationBean来初始化用户名,密码以及能访问Druid监控的黑白名单。

FilterRegistrationBean:请求过滤器,即通过设置过滤器来放行哪些资源。

JAVA 复制代码
@Bean
@ConditionalOnClass(DruidDataSource.class)
public ServletRegistrationBean<StatViewServlet> setStatViewServlet(){
    //设置servlet访问的路径
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
    Map<String, String> params = new HashMap<>(4);
    params.put("loginUsername","admin");
    params.put("loginPassword","123456");
    //允许哪些ip可以进行访问,即ip白名单
    params.put("allow","127.0.0.1");
    //禁止哪些ip可以进行访问,即ip黑名单
    params.put("deny","");
    registrationBean.setInitParameters(params);
    return registrationBean;
}

@Bean
@ConditionalOnClass(DruidDataSource.class)
public FilterRegistrationBean<javax.servlet.Filter> webStatFilter(){
    FilterRegistrationBean<javax.servlet.Filter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new WebStatFilter());
    Map<String, String> params = new HashMap<>(1);
    params.put("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    bean.setInitParameters(params);
    bean.setUrlPatterns(Collections.singletonList("/*"));
    return bean;
}

完成上面的设置后,直接进行访问,地址:http://localhost:端口/项目名称/druid。其中端口为SpringBoot设置的端口,即server.port设置,不设置默认:8080。

项目名称:即设置项目的前缀,即server.servlet.context-path设置,不设置时不需要填写。

页面访问后,则跳转到登录页,填入上述配置的登录用户名、密码即可进入监控页。如图:

2.2 首页

首页即展示Druid使用的版本,驱动及其JDK版本等信息。

2.3 数据源

当有接口被访问时,就会显示当前被访问接口使用到的数据源信息,例如:接口使用了我们上述配置的数据源时,则会显示上面数据源信息。

2.4 SQL监控

Druid的SQL监控,是一项关于SQL执行最慢时间的统计,只显示这一个值,当项目重启时,这些记录就会重置,因此需要进行相应日志输出策略。

2.4.1 配置慢SQL及SQL日志过滤器

在创建的Druid配置类中添加相关过滤器

JAVA 复制代码
@Bean(name = "statFilter")
public StatFilter statFilter(){
    StatFilter statFilter = new StatFilter();
    statFilter.setSlowSqlMillis(30L);
    statFilter.setLogSlowSql(true);
    statFilter.setMergeSql(true);
    return statFilter;
}

@Bean(name = "logFilter")
public Slf4jLogFilter logFilter(){
    Slf4jLogFilter logFilter = new Slf4jLogFilter();
    logFilter.setDataSourceLogEnabled(true);
    logFilter.setStatementExecutableSqlLogEnable(true);
    return logFilter;
}

2.4.2 数据源添加过滤器

在之前定义的数据源方法中,添加上述定义的两个过滤器。

JAVA 复制代码
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource baseDataSource(){
    DruidDataSource build = DruidDataSourceBuilder.create().build();
    List<Filter> filters = new ArrayList<>();
    filters.add(statFilter());
    filters.add(logFilter());
    build.setProxyFilters(filters);
    return build;

}

配置完成后,则直接访问接口,则会看到运行的SQL信息。

2.5 web应用

web应用显示当前项目的一些基本信息。如图:

2.6 URI监控

URI监控显示访问接口路径的一些基本信息。

2.7 Session监控

见名知意也就是监控系统中创建的Session信息。

2.8 Spring监控

在Druid中默认没有开启Spring监控,要开启Spring监控则需要进行相关配置。

JAVA 复制代码
/**
 * @author: jiangjs
 * @description: 开启druid中Spring监控功能
 * @date: 2023/11/23 15:18
 **/
@Configuration
@ConditionalOnProperty(name = "druid.spring.enable",havingValue = "true")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DruidAspectConfig {

    @Bean
    public DruidStatInterceptor druidStatInterceptor(){
        return new DruidStatInterceptor();
    }

    @Bean
    @Scope("prototype")
    public JdkRegexpMethodPointcut methodPointcut(){
        JdkRegexpMethodPointcut methodPointcut = new JdkRegexpMethodPointcut();
        methodPointcut.setPatterns("com.*.mapper.*","com.*.service.*");
        return methodPointcut;
    }

    @Bean
    public DefaultPointcutAdvisor pointcutAdvisor(DruidStatInterceptor interceptor,JdkRegexpMethodPointcut pointcut){
        DefaultPointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor();
        pointcutAdvisor.setPointcut(pointcut);
        pointcutAdvisor.setAdvice(interceptor);
        return pointcutAdvisor;

    }
}

@ConditionalOnProperty:在配置文件中添加了是否开启Spring监控,为true时才进行配置。

总体配置可以理解为创建了一个Druid的拦截器,对需要监控的哪些方法进行拦截。然后将创建的拦截器与监控的方法添加入切点中。

注:在启用Spring监控配置,可能会报错:NoClassDefFoundError:org/aspectj/lang/annotation/Pointcut。

这是由于aspectj的版本问题,只需要再次导入aspectj包即可,例如:我导入的是1.9.6版本

org.aspectj

aspectjweaver

1.9.6

3、密码回调

在日常配置数据库信息时,很多时候密码会使用明文,这就会导致安全性问题。所以很多时候我们都会采用密文的方式,只要在填入数据库信息时,进行解密、赋值就可以了,也就是密码回调。

Druid提供了一个DruidPasswordCallBack的类实现了密码回调。

3.1 自定义回调

自定义一个回调类继承DruidPasswordCallback,并重写setProperties这个方法,该方法就是解析密码,设置密码的方法。

JAVA 复制代码
/**
 * @author: jiangjs
 * @description:
 * @date: 2023/11/24 11:17
 **/
@Slf4j
public class RsaDBPasswordCallBack extends DruidPasswordCallback {
    
    //构造方法,传入resourceLoader,用于读取RSA公私钥文件内容
    private final ResourceLoader resourceLoader;
    public RsaDBPasswordCallBack(ResourceLoader resourceLoader){
        this.resourceLoader = resourceLoader;
    }

    private final static String RSA_KEYS_FILE_PATH = "classpath:/static/rsa/rsa_keys.txt";

    @Override
    public void setProperties(Properties properties){
        super.setProperties(properties);
        try(InputStream ism = resourceLoader.getResource(RSA_KEYS_FILE_PATH).getInputStream()){
            String content = new BufferedReader(new InputStreamReader(ism)).lines().collect(Collectors.joining(System.lineSeparator()));
            JSONObject object = JSONObject.parseObject(content);
            String privateKey = object.getString("private");
            String password = properties.getProperty("password");
            char[] charPassword = RSAUtil.decryptByPrivateKey(password,privateKey).toCharArray();
            super.setPassword(charPassword);
        }catch(Exception e){
            log.error("Druid密码回调报错:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

我是自定义了RSA的加解密,所以将公私钥放在了rsa_keys.txt文件,基于hutool定义了一个RSA加解密的方法。

rsa_keys.txt文件内容:

TXT 复制代码
{
"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYlJRCIlfqit/M4C8MmVQZvwk9nD+b6P3UwRwsGjDoa4+Ct7GEZEBcaT3sYHALMRtUkBaDO8zlL2UX3txcUygaPhJjy+OGQyVbUoVB7HLVgrQEncvpdCBeM3xp8KrmjWy4cjO4XBqhpdTNtmQYckvys3KrPI3g0yp1WWafIeTyyQIDAQAB",
"private": "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJiUlEIiV+qK38zgLwyZVBm/CT2cP5vo/dTBHCwaMOhrj4K3sYRkQFxpPexgcAsxG1SQFoM7zOUvZRfe3FxTKBo+EmPL44ZDJVtShUHsctWCtASdy+l0IF4zfGnwquaNbLhyM7hcGqGl1M22ZBhyS/Kzcqs8jeDTKnVZZp8h5PLJAgMBAAECgYBDYRGbpujmFU/BfJqqWKeP+xHMmExqwFgfdOvI+gPxD1a/pfkJevAFHyCOWwP/nT7xd/PJHkZcSzK8eNkdGEfTpzEkoxngNgAJADkPl4O4kEHmFG2rJ8yg/EfbG+P6SCyZethm+YySgB2qdBgtYQj/fHNWZeEN8HzAcDc16Bwm9QJBAN2/Ep1dS5HGzyrmCs5C0+j9ff2+Vik98nRCp9VVBAPH/BfWwMX4dYF2uFctuPx98qnSoP/cbk2S01inVicFTLsCQQCwJlkCBsnHs8ZP6/8hD0AYPHHsdF1dnw4JpRiOkO16cqVypTOl/SBu6qcGgNk3vaGW33taO7/oNKI7soCLiuhLAkA3bBY+9p54pPQKiMySmOlWBmWDEht+21jJ7g78pu8F8unzG127HUphPUb9oxPlJ6WLBHXw/SskgFoKgmhqAE0ZAkAxY18bYVnb4zzcFbgfxc3bvb7XZDz1Te46qA59kobzCQf4X/deN92LG8Ge4iuFJGcVDS3hu9TvvyopgL/n4BeHAkBUr6kY56hO3RyGsgtVu8t5oFHR8K1C+322g9t4y7akoSBxdIYDvT6wr+Q0gCj6zSP+/mZG9wb8BWeFnPUShRAo"
}

RSAUtil工具类:

JAVA 复制代码
/**
 * @author: jiangjs
 * @description: 基于hutool工具的RSA加解密
 * @date: 2023/11/13 10:17
 **/
@Component
public class RSAUtil {

    private final static KeyPair keyPair = SecureUtil.generateKeyPair("RSA");

    /**
     * 获取RSA公钥
     * @return base64的公钥
     */
    public static String generatePublicKey(){
        return Base64.encode(keyPair.getPublic().getEncoded());
    }

    /**
     * 获取RSA私钥
     * @return base64的私钥
     */
    public static String generatePrivateKey(){
        return Base64.encode(keyPair.getPrivate().getEncoded());
    }

    /**
     * 内容进行私钥加密
     * @param content 需加密内容
     * @param privateKey 私钥
     * @return 加密后信息
     */
    public static String encryptByPrivateKey(String content,String privateKey){
        RSA rsa = new RSA(privateKey,null);
        return rsa.encryptHex(content, KeyType.PrivateKey);
    }

    /**
     * 内容进行加密
     * @param content 需加密内容
     * @param publicKey 公钥
     * @return 加密后信息
     */
    public static String encryptByPublicKey(String content,String publicKey,String privateKey){
        RSA rsa = new RSA(privateKey.replace(" ","+"),publicKey.replace(" ","+"));
        return rsa.encryptBase64(content, KeyType.PublicKey);
    }

    /**
     * RSA私钥解密
     * @param encryptContent 公钥加密内容
     * @param privateKey 私钥
     * @return 解密后的信息
     */
    public static String decryptByPrivateKey(String encryptContent,String privateKey){
        RSA rsa = new RSA(privateKey, null);
        return rsa.decryptStr(encryptContent, KeyType.PrivateKey, StandardCharsets.UTF_8);
    }

    /**
     * RSA公钥解密
     * @param encryptContent 私钥加密后的内容
     * @param publicKey 公钥
     * @return 解密后的信息
     */
    public static String decryptByPublicKey(String encryptContent,String publicKey){
        RSA rsa = new RSA(null, publicKey);
        return rsa.decryptStr(encryptContent, KeyType.PublicKey,StandardCharsets.UTF_8);
    }
}

3.2 设置配置

在Druid的配置文件中设置PasswordCallback,初始化自定义的密码回调类。

JAVA 复制代码
@Configuration
public class DruidDataSourceConfig {

    @Resource
    private ResourceLoader resourceLoader;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource baseDataSource(){
        DruidDataSource build = DruidDataSourceBuilder.create().build();
        build.setPasswordCallback(new RsaDBPasswordCallBack(resourceLoader));
        return build;

    }
}

3.3 设置配置文件

在application.yml中进行相关信息配置

YAML 复制代码
spring:
  #数据库连接
  datasource:
    url: jdbc:mysql://127.0.0.1:3308/warehouse?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&useUnicode=true
    username: root
    password: ""
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 15
      min-idle: 15
      max-active: 200
      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: false
      connection-properties: false;password=${spring.datasource.druid.password}
      filter:
        config:
          enabled: true
      #密码回调
      #password-callback: com.jiashn.minio.config.RsaDBPasswordCallBack
      password: BePkkx2NHPYt9Dk9SFdYzqr0ZdFqVvELoD2tJ7awIcrDz1wtf6HpKqklDeph+TEt3btnshloolfu4nO/rIK2kqfCpTTz/pFhTyLJ1PN2U7PVb5bT89gpKOFKNM/tBx5/VH1BigciDNNPSFuQwwN6w7zNPLoq3Da/iFPc38znYT4=
     # connect-properties: password=${spring.datasource.druid.password}

设置主要参数为:

spring.datasource.druid.connection-properties:连接属性信息。

spring.datasource.druid.password:加密后的密码。

上述设置好后,启动项目就可以愉快地进行测试了。

【踩坑记】

(1)在实现密码回调时,参考网上一些实现代码,在配置文件中配置password-callback,指定自定义密码回调类路径。启动时会报错:

提示信息这个属性需填写String类型的值,因此采用在配置文件中直接进行配置来解决这个问题。

(2)由于版本问题,很多小伙伴会将加密后的密码放置在connect-properties上,会导致在自定义的密码回调类设置密码时,获取不到密码。实际这个属性(setProperties(Properties properties))对应的配置是:spring.datasource.druid.connection-properties

好了,今天就分享到这了,感谢小伙伴们能阅读到这。

相关推荐
建投数据1 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
向前看-1 小时前
验证码机制
前端·后端
Hacker_LaoYi2 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀2 小时前
Redis梳理
数据库·redis·缓存
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天2 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭3 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
工业甲酰苯胺3 小时前
分布式系统架构:服务容错
数据库·架构
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
独行soc4 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘