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

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

相关推荐
月光水岸New18 分钟前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山67519 分钟前
数据库基础1
数据库
我爱松子鱼22 分钟前
mysql之规则优化器RBO
数据库·mysql
闲猫24 分钟前
go orm GORM
开发语言·后端·golang
丁卯4041 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo1 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Tirzano1 小时前
springsecurity自定义认证
spring boot·spring
Rverdoser2 小时前
【SQL】多表查询案例
数据库·sql
Galeoto2 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)2 小时前
MySQL主从架构
服务器·数据库·mysql