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。
好了,今天就分享到这了,感谢小伙伴们能阅读到这。