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);
}
}
}