Springboot+Druid 动态数据源配置监控

一、引入maven依赖,使用 starter 与原生 druid 依赖配置有所不同

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>

二、配置数据源

spring:
  datasource:
    druid:
      filter:
        stat: #开启sql监控
          enabled: true
        wall:
          enabled: true
        slf4j:
          enabled: true
      DB1:
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:sqlserver://127.0.0.1:1487;DatabaseName=CN2018
        username: root
        password: 12345
        driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
        # 连接池的配置信息
        initial-size: 5
        min-idle: 5
        maxActive: 20
        maxWait: 60000 # 配置获取连接等待超时的时间
        timeBetweenEvictionRunsMillis: 60000   # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        minEvictableIdleTimeMillis: 300000     # 配置一个连接在池中最小生存的时间,单位是毫秒
#        validationQuery: SELECT 1 FROM DUAL   # mysql数据库
        validationQuery: SELECT 1              # sqlserver 数据库
        poolPreparedStatements: true           # 打开PSCache,并且指定每个连接上PSCache的大小
        maxPoolPreparedStatementPerConnectionSize: 20
      DB2:
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:sqlserver://127.0.0.1:1488;DatabaseName=DB2022
        username: root
        password: 123456
        driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
        # 连接池的配置信息
        initial-size: 1
        min-idle: 1
        maxActive: 5
        maxWait: 60000 # 配置获取连接等待超时的时间
        timeBetweenEvictionRunsMillis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        minEvictableIdleTimeMillis: 300000    # 配置一个连接在池中最小生存的时间,单位是毫秒
#        validationQuery: SELECT 1 FROM DUAL  # mysql数据库
        validationQuery: SELECT 1             # sqlServer 数据库
        poolPreparedStatements: true          # 打开PSCache,并且指定每个连接上PSCache的大小
        maxPoolPreparedStatementPerConnectionSize: 20

三、创建配置类

//记录数据库名
public interface ContextConst {
    enum DataSourceType{
        DB1,DB2
    }
}
//数据源持有类
@Slf4j
public class DataSourceContextHolder {
    /**
     * CONTEXT_HOLDER代表一个可以存放String类型的ThreadLocal对象,
     * 此时任何一个线程可以并发访问这个变量,
     * 对它进行写入、读取操作,都是线程安全的。
     * 比如一个线程通过CONTEXT_HOLDER.set(“aaaa”);将数据写入ThreadLocal中,
     * 在任何一个地方,都可以通过CONTEXT_HOLDER.get();将值获取出来。
     * 这里写入的就是数据库名,
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dbType){
        CONTEXT_HOLDER.set(dbType);
    }

    public static String getDataSource(){
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSource(){
        CONTEXT_HOLDER.remove();
    }
}
//数据源路由实现类
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * @Description:数据源路由实现类 AbstractRoutingDataSource(每执行一次数据库 , 动态获取DataSource)
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}
//自定义切换数据源注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDateSouce {
    ContextConst.DataSourceType value() default ContextConst.DataSourceType.DB1;
}
//AOP动态数据源通知
@Component
@Aspect
@Order(-1) //保证在@Transactional之前执行,必须加上,不然无法分辨是哪个数据源在执行事务
@Slf4j
public class DynamicDataSourceAspect {

    @Before("execution(* com.blaze.pboc.service..*.*(..))")
    public void before(JoinPoint point) {
        try {
            TargetDateSouce annotationOfClass = point.getTarget().getClass().getAnnotation(TargetDateSouce.class);
            String methodName = point.getSignature().getName();
            Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
            Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
            TargetDateSouce methodAnnotation = method.getAnnotation(TargetDateSouce.class);
            methodAnnotation = methodAnnotation == null ? annotationOfClass : methodAnnotation;
            ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() != null ? methodAnnotation.value() : ContextConst.DataSourceType.DB1;
            DataSourceContextHolder.setDataSource(dataSourceType.name());
        } catch (NoSuchMethodException e) {
            log.error("error", e);
        }
    }

    @After("execution(* com.blaze.pboc.service..*.*(..))")
    public void after(JoinPoint point) {
        DataSourceContextHolder.clearDataSource();
    }
}
//动态数据源配置类
@SpringBootConfiguration
public class DruidDataSourceConfig {

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

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DruidDataSource clusterDataSource() {
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return druidDataSource;
    }

    @Primary
    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        //配置多数据源这里的key一定要是string类型,枚举类型并不支持,所以用到枚举中name()方法转成string,或者用toString方法。
        HashMap<Object, Object> dataSourceMap = new HashMap();
        dataSourceMap.put(ContextConst.DataSourceType.DB1.name(), masterDataSource());
        dataSourceMap.put(ContextConst.DataSourceType.DB2.name(), clusterDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    // 配置@Transactional注解事务
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

    //配置 Druid 监控管理后台的Servlet;
    //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
    @Bean
    public ServletRegistrationBean registrationBean() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("loginUsername", "admin");
        initParameters.put("loginPassword", "12345");
        bean.setInitParameters(initParameters);
        return bean;
    }

    //去除Druid监控页面的广告
    @Bean
    public FilterRegistrationBean removeDruidAdFilter() throws IOException {
        String text = Utils.readFromResource("support/http/resources/js/common.js");
        final String newJs = text.replace("this.buildFooter();", "");
        // 新建一个过滤器注册器对象
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        // 注册common.js文件的过滤器
        registration.addUrlPatterns("/druid/js/common.js");
        // 添加一个匿名的过滤器对象,并把改造过的common.js文件内容写入到浏览器
        registration.setFilter((servletRequest, servletResponse, filterChain) -> {
            // 重置缓冲区,响应头不会被重置
            servletResponse.resetBuffer();
            // 把改造过的common.js文件内容写入到浏览器
            servletResponse.getWriter().write(newJs);
        });
        return registration;
    }

}

四、测试,常用的数据源配置db1,无需添加注解

 

五、登陆 web 监控端

http://127.0.0.1:9000/api/druid/login.html

 

 

posted @ 2022-01-14 18:41  远离颠倒梦想  阅读(1054)  评论(0编辑  收藏  举报