SpringBoot整合Druid数据库连接池&多数据源&注解切换&动态添加

时间:2025-02-15 09:50:57

1. 引入依赖

            <!-- 阿里数据库连接池 -->
            <dependency>
                <groupId></groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.16</version>
            </dependency>

            <!-- Mysql驱动包 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>

2. 配置数据源

spring:
  datasource:
    # 数据源基本配置
    username: 账号
    password: 密码
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    # driver-class需要注意mysql驱动的版本( 或 )
    driver-class-name: 
    type: 
    # Druid的其他属性配置
    druid:
      # 初始化时建立物理连接的个数
      initial-size: 10
      # 连接池的最小空闲数量
      min-idle: 5
      # 连接池最大连接数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-eviction-runs-millis: 60000
      # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
      min-evictable-idle-time-millis: 30000
      # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
      validation-query: SELECT 1 FROM DUAL
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
      pool-prepared-statements: false
      # 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入
      filters: stat,wall,slf4j
      # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
      max-pool-prepared-statement-per-connection-size: -1
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: =true;=5000
      web-stat-filter:
        # 是否启用StatFilter默认值true
        enabled: true
        # 添加过滤规则
        url-pattern: /*
        # 忽略过滤的格式
        exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico

      stat-view-servlet:
        # 是否启用StatViewServlet默认值true
        enabled: true
        # 访问路径为/druid时,跳转到StatViewServlet
        url-pattern: /druid/*
        # 是否能够重置数据
        reset-enable: false
        # 需要账号密码才能访问控制台,默认为root
        login-username: root
        login-password: 123456
        # IP白名单
        allow: 127.0.0.1
        # IP黑名单(共同存在时,deny优先于allow)
        deny:

配置好之后 Druid 会通过 DruidDataSourceAutoConfigure 自动装配

package ;

@Configuration
@ConditionalOnClass()
@AutoConfigureBefore()
@EnableConfigurationProperties({, })
@Import({,
        ,
        ,
        })
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = ();

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        ("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}

3. Druid 配置多数据源

属性配置

# 数据源配置
spring:
    datasource:
        type: 
        driverClassName: 
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://localhost:3306/large_screen_monitor?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: root
            # 从库数据源
            slave:
                # 从数据源开关/默认关闭
                enabled: false
                url: 
                username: 
                password: 
            # 初始连接数
            initialSize: 5
            # 最小连接池数量
            minIdle: 10
            # 最大连接池数量
            maxActive: 20
            # 配置获取连接等待超时的时间
            maxWait: 60000
            # 配置连接超时时间
            connectTimeout: 30000
            # 配置网络超时时间
            socketTimeout: 60000
            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            timeBetweenEvictionRunsMillis: 60000
            # 配置一个连接在池中最小生存的时间,单位是毫秒
            minEvictableIdleTimeMillis: 300000
            # 配置一个连接在池中最大生存的时间,单位是毫秒
            maxEvictableIdleTimeMillis: 900000
            # 配置检测连接是否有效
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
            webStatFilter: 
                enabled: true
            statViewServlet:
                enabled: true
                # 设置白名单,不填则允许所有访问
                allow:
                url-pattern: /druid/*
                # 控制台管理用户名和密码
                login-username: webserver
                login-password: 123456
            filter:
                stat:
                    enabled: true
                    # 慢SQL记录
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
package ;

import ;
import ;
import ;

/**
 * druid 配置属性
 */
@Configuration
public class DruidProperties
{
    @Value("${}")
    private int initialSize;

    @Value("${}")
    private int minIdle;

    @Value("${}")
    private int maxActive;

    @Value("${}")
    private int maxWait;

    @Value("${}")
    private int connectTimeout;

    @Value("${}")
    private int socketTimeout;

    @Value("${}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${}")
    private int minEvictableIdleTimeMillis;

    @Value("${}")
    private int maxEvictableIdleTimeMillis;

    @Value("${}")
    private String validationQuery;

    @Value("${}")
    private boolean testWhileIdle;

    @Value("${}")
    private boolean testOnBorrow;

    @Value("${}")
    private boolean testOnReturn;

    public DruidDataSource dataSource(DruidDataSource datasource)
    {
        /** 配置初始化大小、最小、最大 */
        (initialSize);
        (maxActive);
        (minIdle);

        /** 配置获取连接等待超时的时间 */
        (maxWait);
        
        /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
        (connectTimeout);
        
        /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
        (socketTimeout);

        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
        (timeBetweenEvictionRunsMillis);

        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
        (minEvictableIdleTimeMillis);
        (maxEvictableIdleTimeMillis);

        /**
         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
         */
        (validationQuery);
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
        (testWhileIdle);
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        (testOnBorrow);
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        (testOnReturn);
        return datasource;
    }
}

数据源枚举

package ;

/**
 * 数据源
 */
public enum DataSourceType
{
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE
}

动态数据源

继承 AbstractRoutingDataSource 就可以实现动态数据源了

实现了一个动态数据源类的构造方法,主要是为了设置默认数据源,以及以Map保存的各种目标数据源。其中Map的key是设置的数据源名称,value则是对应的数据源(DataSource)。

在使用 DynamicDataSource 时,会调用determineCurrentLookupKey 方法拿到具体数据源的key,然后使用对应的数据源;如果没有对应的数据源,会使用 DynamicDataSource 设置的默认数据源

package ;

import ;
import ;
import ;

/**
 * 动态数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        (defaultTargetDataSource);
        (targetDataSources);
        ();
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        return ();
    }
}

数据源切换

package ;

import org.;
import org.;

/**
 * 数据源切换处理
 */
public class DynamicDataSourceContextHolder
{
    public static final Logger log = ();

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(String dsType)
    {
        ("切换到{}数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getDataSourceType()
    {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType()
    {
        CONTEXT_HOLDER.remove();
    }
}

 数据源配置类

通过配置类,将配置文件中的配置的数据库信息转换成datasource,并添加到DynamicDataSource中,同时通过@Bean将DynamicDataSource注入Spring中进行管理,后期在进行动态数据源添加时,会用到。

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * druid 配置多数据源
 */
@Configuration
public class DruidConfig
{
    @Bean
    @ConfigurationProperties("")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = ().build();
        return (dataSource);
    }

    @Bean
    @ConfigurationProperties("")
    @ConditionalOnProperty(prefix = "", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = ().build();
        return (dataSource);
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        ((), masterDataSource);
        setDataSource(targetDataSources, (), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
    
    /**
     * 设置数据源
     * 
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = (beanName);
            (sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }

    /**
     * 去除监控页面底部的广告
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    @ConditionalOnProperty(name = "", havingValue = "true")
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
    {
        // 获取web监控页面的参数
         config = ();
        // 提取的配置路径
        String pattern = () != null ? () : "/druid/*";
        String commonJsPattern = ("\\*", "js/");
        final String filePath = "support/http/resources/js/";
        // 创建filter进行过滤
        Filter filter = new Filter()
        {
            @Override
            public void init( filterConfig) throws ServletException
            {
            }
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException
            {
                (request, response);
                // 重置缓冲区,响应头不会被重置
                ();
                // 获取
                String text = (filePath);
                // 正则替换banner, 除去底部的广告信息
                text = ("<a.*?banner\"></a><br/>", "");
                text = ("powered.*?</a>", "");
                ().write(text);
            }
            @Override
            public void destroy()
            {
            }
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        (filter);
        (commonJsPattern);
        return registrationBean;
    }
}

SpringBoot Bean工具类

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 * 
 * @author webserver
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    {
         = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
    {
         = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws 
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) (name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws 
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) (clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return (name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws 
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return (name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws 
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return (name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws 
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return (name);
    }

    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) ();
    }

    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */
    public static String[] getActiveProfiles()
    {
        return ().getActiveProfiles();
    }

    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */
    public static String getActiveProfile()
    {
        final String[] activeProfiles = getActiveProfiles();
        return (activeProfiles != null &&  > 0) ? activeProfiles[0] : null;
    }

    /**
     * 获取配置文件中的值
     *
     * @param key 配置文件的key
     * @return 当前的配置文件的值
     *
     */
    public static String getRequiredProperty(String key)
    {
        return ().getRequiredProperty(key);
    }
}

手动切换数据源

在需要切换数据源的方法中使用DynamicDataSourceContextHolder类实现手动切换

public List<SysUser> selectUserList(SysUser user)
{
	(());
	List<SysUser> userList = (user);
	();
	return userList;
}

4.实现注解切换数据源

自定义注解

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * 自定义多数据源切换注解
 *
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 *
 * @author webserver
 */
@Target({ ,  })
@Retention()
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切换数据源名称
     */
    public DataSourceType value() default ;
}

根据注解实现aop切点 

代码使用了@Around,通过ProceedingJoinPoint获取注解信息,拿到注解传递值,然后设置当前线程的数据源。

import ;
import ;
import ;
import ;
import ;
import ;
import org.;
import org.;
import ;
import ;
import ;
import ;
import ;

/**
 * 多数据源处理
 * 
 * @author webserver
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
    protected Logger logger = (getClass());

    @Pointcut("@annotation()"
            + "|| @within()")
    public void dsPointCut()
    {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        DataSource dataSource = getDataSource(point);

        if (dataSource != null)
        {
            (().name());
        }

        try
        {
            return ();
        }
        finally
        {
            // 销毁数据源 在执行方法之后
            ();
        }
    }

    /**
     * 获取需要切换的数据源
     */
    public DataSource getDataSource(ProceedingJoinPoint point)
    {
        MethodSignature signature = (MethodSignature) ();
        DataSource dataSource = ((), );
        if ((dataSource))
        {
            return dataSource;
        }

        return ((), );
    }
}

使用注解切换数据源

在需要使用多数据源方法或类上添加@DataSource注解,其中value用来表示数据源

@DataSource(value = )
public List<SysUser> selectUserList(SysUser user)
{
	return (user);
}

5. 动态添加数据源

数据源实体类

@Data
@Accessors(chain = true)
public class DataSourceEntity {

    /**
     * 数据库地址
     */
    private String url;
    /**
     * 数据库用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String passWord;
    /**
     * 数据库驱动
     */
    private String driverClassName;
    /**
     * 数据库key,即保存DynamicDataSource中Map的key
     */
    private String key;
}

修改DynamicDataSource

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    private final Map<Object,Object> targetDataSourceMap;

    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        (defaultDataSource);
        (targetDataSources);
         = targetDataSources;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return ();
    }

    /**
     * 添加数据源信息
     * @param dataSources 数据源实体集合
     * @return 返回添加结果
     */
    public void createDataSource(List<DataSourceEntity> dataSources){
        try {
            if ((dataSources)){
                for (DataSourceEntity ds : dataSources) {
                    //校验数据库是否可以连接
                    (());
                    ((),(),());
                    //定义数据源
                    DruidDataSource dataSource = new DruidDataSource();
                    (ds,dataSource);
                    //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
                    (true);
                    //建议配置为true,不影响性能,并且保证安全性。
                    //申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
                    (true);
                    //用来检测连接是否有效的sql,要求是一个查询语句。
                    ("select 1 ");
                    ();
                    ((),dataSource);
                }
                ();
                // 将TargetDataSources中的连接信息放入resolvedDataSources管理
                ();
                return ;
            }
        }catch (ClassNotFoundException | SQLException e) {
            ("---程序报错---:{}", ());
        }
        return ;
    }

    /**
     * 校验数据源是否存在
     * @param key 数据源保存的key
     * @return 返回结果,true:存在,false:不存在
     */
    public boolean existsDataSource(String key){
        return ((key));
    }
}

存储数据源信息的表

create table test_db_info(
    id int auto_increment primary key not null comment '主键Id',
    url varchar(255) not null comment '数据库URL',
    username varchar(255) not null comment '用户名',
    password varchar(255) not null comment '密码',
    driver_class_name varchar(255) not null comment '数据库驱动'
    name varchar(255) not null comment '数据库名称'
)

insert into test_db_info(url, username, password,driver_class_name, name)
value ('jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false',
       'root','123456','','add_slave')

SpringBoot启动时加载数据源信息

@Component
public class LoadDataSourceRunner implements CommandLineRunner {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private TestDbInfoMapper testDbInfoMapper;
    @Override
    public void run(String... args) throws Exception {
        List<TestDbInfo> testDbInfos = (null);
        if ((testDbInfos)) {
            List<DataSourceEntity> ds = new ArrayList<>();
            for (TestDbInfo testDbInfo : testDbInfos) {
                DataSourceEntity sourceEntity = new DataSourceEntity();
                (testDbInfo,sourceEntity);
                (());
                (sourceEntity);
            }
            (ds);
        }
    }
}