1. 引入依賴
<!-- 阿里數(shù)據(jù)庫連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<!-- Mysql驅(qū)動包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2. 配置數(shù)據(jù)源
spring:
datasource:
# 數(shù)據(jù)源基本配置
username: 賬號
password: 密碼
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
# driver-class需要注意mysql驅(qū)動的版本(com.mysql.cj.jdbc.Driver 或 com.mysql.jdbc.Driver)
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# Druid的其他屬性配置
druid:
# 初始化時建立物理連接的個數(shù)
initial-size: 10
# 連接池的最小空閑數(shù)量
min-idle: 5
# 連接池最大連接數(shù)量
max-active: 20
# 獲取連接時最大等待時間,單位毫秒
max-wait: 60000
# 申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
test-while-idle: true
# 既作為檢測的間隔時間又作為testWhileIdel執(zhí)行的依據(jù)
time-between-eviction-runs-millis: 60000
# 銷毀線程時檢測當(dāng)前連接的最后活動時間和當(dāng)前時間差大于該值時,關(guān)閉當(dāng)前連接(配置連接在池中的最小生存時間)
min-evictable-idle-time-millis: 30000
# 用來檢測數(shù)據(jù)庫連接是否有效的sql 必須是一個查詢語句(oracle中為 select 1 from dual)
validation-query: SELECT 1 FROM DUAL
# 申請連接時會執(zhí)行validationQuery檢測連接是否有效,開啟會降低性能,默認(rèn)為true
test-on-borrow: false
# 歸還連接時會執(zhí)行validationQuery檢測連接是否有效,開啟會降低性能,默認(rèn)為true
test-on-return: false
# 是否緩存preparedStatement, 也就是PSCache,PSCache對支持游標(biāo)的數(shù)據(jù)庫性能提升巨大,比如說oracle,在mysql下建議關(guān)閉。
pool-prepared-statements: false
# 置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,stat: 監(jiān)控統(tǒng)計、Slf4j:日志記錄、waLL: 防御sqL注入
filters: stat,wall,slf4j
# 要啟用PSCache,必須配置大于0,當(dāng)大于0時,poolPreparedStatements自動觸發(fā)修改為true。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題,可以把這個數(shù)值配置大一些,比如說100
max-pool-prepared-statement-per-connection-size: -1
# 合并多個DruidDataSource的監(jiān)控數(shù)據(jù)
use-global-data-source-stat: true
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
web-stat-filter:
# 是否啟用StatFilter默認(rèn)值true
enabled: true
# 添加過濾規(guī)則
url-pattern: /*
# 忽略過濾的格式
exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico
stat-view-servlet:
# 是否啟用StatViewServlet默認(rèn)值true
enabled: true
# 訪問路徑為/druid時,跳轉(zhuǎn)到StatViewServlet
url-pattern: /druid/*
# 是否能夠重置數(shù)據(jù)
reset-enable: false
# 需要賬號密碼才能訪問控制臺,默認(rèn)為root
login-username: root
login-password: 123456
# IP白名單
allow: 127.0.0.1
# IP黑名單(共同存在時,deny優(yōu)先于allow)
deny:
配置好之后 Druid 會通過 DruidDataSourceAutoConfigure 自動裝配
package com.alibaba.druid.spring.boot.autoconfigure;
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
DruidStatViewServletConfiguration.class,
DruidWebStatFilterConfiguration.class,
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
3.?Druid 配置多數(shù)據(jù)源
屬性配置
# 數(shù)據(jù)源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主庫數(shù)據(jù)源
master:
url: jdbc:mysql://localhost:3306/large_screen_monitor?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
# 從庫數(shù)據(jù)源
slave:
# 從數(shù)據(jù)源開關(guān)/默認(rèn)關(guān)閉
enabled: false
url:
username:
password:
# 初始連接數(shù)
initialSize: 5
# 最小連接池數(shù)量
minIdle: 10
# 最大連接池數(shù)量
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置連接超時時間
connectTimeout: 30000
# 配置網(wǎng)絡(luò)超時時間
socketTimeout: 60000
# 配置間隔多久才進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一個連接在池中最大生存的時間,單位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置檢測連接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 設(shè)置白名單,不填則允許所有訪問
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 com.test.framework.config.properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
/**
* druid 配置屬性
*/
@Configuration
public class DruidProperties
{
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.connectTimeout}")
private int connectTimeout;
@Value("${spring.datasource.druid.socketTimeout}")
private int socketTimeout;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource)
{
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置獲取連接等待超時的時間 */
datasource.setMaxWait(maxWait);
/** 配置驅(qū)動連接超時時間,檢測數(shù)據(jù)庫建立連接的超時時間,單位是毫秒 */
datasource.setConnectTimeout(connectTimeout);
/** 配置網(wǎng)絡(luò)超時時間,等待數(shù)據(jù)庫操作完成的網(wǎng)絡(luò)超時時間,單位是毫秒 */
datasource.setSocketTimeout(socketTimeout);
/** 配置間隔多久才進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一個連接在池中最小、最大生存的時間,單位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
數(shù)據(jù)源枚舉
package com.test.common.enums;
/**
* 數(shù)據(jù)源
*/
public enum DataSourceType
{
/**
* 主庫
*/
MASTER,
/**
* 從庫
*/
SLAVE
}
動態(tài)數(shù)據(jù)源
繼承 AbstractRoutingDataSource 就可以實(shí)現(xiàn)動態(tài)數(shù)據(jù)源了
實(shí)現(xiàn)了一個動態(tài)數(shù)據(jù)源類的構(gòu)造方法,主要是為了設(shè)置默認(rèn)數(shù)據(jù)源,以及以Map保存的各種目標(biāo)數(shù)據(jù)源。其中Map的key是設(shè)置的數(shù)據(jù)源名稱,value則是對應(yīng)的數(shù)據(jù)源(DataSource)。
在使用 DynamicDataSource 時,會調(diào)用determineCurrentLookupKey
?方法拿到具體數(shù)據(jù)源的key,然后使用對應(yīng)的數(shù)據(jù)源;如果沒有對應(yīng)的數(shù)據(jù)源,會使用 DynamicDataSource 設(shè)置的默認(rèn)數(shù)據(jù)源
package com.test.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 動態(tài)數(shù)據(jù)源
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
數(shù)據(jù)源切換
package com.test.framework.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 數(shù)據(jù)源切換處理
*/
public class DynamicDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal維護(hù)變量,ThreadLocal為每個使用該變量的線程提供獨(dú)立的變量副本,
* 所以每一個線程都可以獨(dú)立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源的變量
*/
public static void setDataSourceType(String dsType)
{
log.info("切換到{}數(shù)據(jù)源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 獲得數(shù)據(jù)源的變量
*/
public static String getDataSourceType()
{
return CONTEXT_HOLDER.get();
}
/**
* 清空數(shù)據(jù)源變量
*/
public static void clearDataSourceType()
{
CONTEXT_HOLDER.remove();
}
}
?數(shù)據(jù)源配置類
通過配置類,將配置文件中的配置的數(shù)據(jù)庫信息轉(zhuǎn)換成datasource,并添加到DynamicDataSource
中,同時通過@Bean將DynamicDataSource
注入Spring中進(jìn)行管理,后期在進(jìn)行動態(tài)數(shù)據(jù)源添加時,會用到。
package com.test.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.test.common.enums.DataSourceType;
import com.test.common.utils.spring.SpringUtils;
import com.test.framework.config.properties.DruidProperties;
import com.test.framework.datasource.DynamicDataSource;
/**
* druid 配置多數(shù)據(jù)源
*/
@Configuration
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource)
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
/**
* 設(shè)置數(shù)據(jù)源
*
* @param targetDataSources 備選數(shù)據(jù)源集合
* @param sourceName 數(shù)據(jù)源名稱
* @param beanName bean名稱
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
{
try
{
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
}
catch (Exception e)
{
}
}
/**
* 去除監(jiān)控頁面底部的廣告
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
{
// 獲取web監(jiān)控頁面的參數(shù)
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路徑
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 創(chuàng)建filter進(jìn)行過濾
Filter filter = new Filter()
{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
chain.doFilter(request, response);
// 重置緩沖區(qū),響應(yīng)頭不會被重置
response.resetBuffer();
// 獲取common.js
String text = Utils.readFromResource(filePath);
// 正則替換banner, 除去底部的廣告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy()
{
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}
SpringBoot Bean工具類
package com.test.common.utils.spring;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* spring工具類 方便在非spring管理環(huán)境中獲取bean
*
* @author webserver
*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
/** Spring應(yīng)用上下文環(huán)境 */
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
SpringUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
SpringUtils.applicationContext = applicationContext;
}
/**
* 獲取對象
*
* @param name
* @return Object 一個以所給名字注冊的bean的實(shí)例
* @throws org.springframework.beans.BeansException
*
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
return (T) beanFactory.getBean(name);
}
/**
* 獲取類型為requiredType的對象
*
* @param clz
* @return
* @throws org.springframework.beans.BeansException
*
*/
public static <T> T getBean(Class<T> clz) throws BeansException
{
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* 如果BeanFactory包含一個與所給名稱匹配的bean定義,則返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name)
{
return beanFactory.containsBean(name);
}
/**
* 判斷以給定名字注冊的bean定義是一個singleton還是一個prototype。 如果與給定名字相應(yīng)的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.isSingleton(name);
}
/**
* @param name
* @return Class 注冊對象的類型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getType(name);
}
/**
* 如果給定的bean名字在bean定義中有別名,則返回這些別名
*
* @param name
* @return
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getAliases(name);
}
/**
* 獲取aop代理對象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker)
{
return (T) AopContext.currentProxy();
}
/**
* 獲取當(dāng)前的環(huán)境配置,無配置返回null
*
* @return 當(dāng)前的環(huán)境配置
*/
public static String[] getActiveProfiles()
{
return applicationContext.getEnvironment().getActiveProfiles();
}
/**
* 獲取當(dāng)前的環(huán)境配置,當(dāng)有多個環(huán)境配置時,只獲取第一個
*
* @return 當(dāng)前的環(huán)境配置
*/
public static String getActiveProfile()
{
final String[] activeProfiles = getActiveProfiles();
return (activeProfiles != null && activeProfiles.lenth > 0) ? activeProfiles[0] : null;
}
/**
* 獲取配置文件中的值
*
* @param key 配置文件的key
* @return 當(dāng)前的配置文件的值
*
*/
public static String getRequiredProperty(String key)
{
return applicationContext.getEnvironment().getRequiredProperty(key);
}
}
手動切換數(shù)據(jù)源
在需要切換數(shù)據(jù)源的方法中使用DynamicDataSourceContextHolder
類實(shí)現(xiàn)手動切換
public List<SysUser> selectUserList(SysUser user)
{
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
List<SysUser> userList = userMapper.selectUserList(user);
DynamicDataSourceContextHolder.clearDataSourceType();
return userList;
}
4.實(shí)現(xiàn)注解切換數(shù)據(jù)源
自定義注解
package com.test.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.test.common.enums.DataSourceType;
/**
* 自定義多數(shù)據(jù)源切換注解
*
* 優(yōu)先級:先方法,后類,如果方法覆蓋了類上的數(shù)據(jù)源類型,以方法的為準(zhǔn),否則以類上的為準(zhǔn)
*
* @author webserver
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切換數(shù)據(jù)源名稱
*/
public DataSourceType value() default DataSourceType.MASTER;
}
根據(jù)注解實(shí)現(xiàn)aop切點(diǎn)?
代碼使用了@Around,通過ProceedingJoinPoint
獲取注解信息,拿到注解傳遞值,然后設(shè)置當(dāng)前線程的數(shù)據(jù)源。文章來源:http://www.zghlxwxcb.cn/news/detail-842356.html
import java.util.Objects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.test.common.annotation.DataSource;
import com.test.framework.datasource.DynamicDataSourceContextHolder;
/**
* 多數(shù)據(jù)源處理
*
* @author webserver
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.example.common.annotation.DataSource)"
+ "|| @within(com.example.common.annotation.DataSource)")
public void dsPointCut()
{
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
{
DataSource dataSource = getDataSource(point);
if (dataSource != null)
{
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try
{
return point.proceed();
}
finally
{
// 銷毀數(shù)據(jù)源 在執(zhí)行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 獲取需要切換的數(shù)據(jù)源
*/
public DataSource getDataSource(ProceedingJoinPoint point)
{
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource))
{
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
使用注解切換數(shù)據(jù)源
在需要使用多數(shù)據(jù)源方法或類上添加@DataSource
注解,其中value
用來表示數(shù)據(jù)源文章來源地址http://www.zghlxwxcb.cn/news/detail-842356.html
@DataSource(value = DataSourceType.SLAVE)
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
5. 動態(tài)添加數(shù)據(jù)源
數(shù)據(jù)源實(shí)體類
@Data
@Accessors(chain = true)
public class DataSourceEntity {
/**
* 數(shù)據(jù)庫地址
*/
private String url;
/**
* 數(shù)據(jù)庫用戶名
*/
private String userName;
/**
* 密碼
*/
private String passWord;
/**
* 數(shù)據(jù)庫驅(qū)動
*/
private String driverClassName;
/**
* 數(shù)據(jù)庫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){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
this.targetDataSourceMap = targetDataSources;
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
/**
* 添加數(shù)據(jù)源信息
* @param dataSources 數(shù)據(jù)源實(shí)體集合
* @return 返回添加結(jié)果
*/
public void createDataSource(List<DataSourceEntity> dataSources){
try {
if (CollectionUtils.isNotEmpty(dataSources)){
for (DataSourceEntity ds : dataSources) {
//校驗(yàn)數(shù)據(jù)庫是否可以連接
Class.forName(ds.getDriverClassName());
DriverManager.getConnection(ds.getUrl(),ds.getUserName(),ds.getPassWord());
//定義數(shù)據(jù)源
DruidDataSource dataSource = new DruidDataSource();
BeanUtils.copyProperties(ds,dataSource);
//申請連接時執(zhí)行validationQuery檢測連接是否有效,這里建議配置為TRUE,防止取到的連接不可用
dataSource.setTestOnBorrow(true);
//建議配置為true,不影響性能,并且保證安全性。
//申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
dataSource.setTestWhileIdle(true);
//用來檢測連接是否有效的sql,要求是一個查詢語句。
dataSource.setValidationQuery("select 1 ");
dataSource.init();
this.targetDataSourceMap.put(ds.getKey(),dataSource);
}
super.setTargetDataSources(this.targetDataSourceMap);
// 將TargetDataSources中的連接信息放入resolvedDataSources管理
super.afterPropertiesSet();
return Boolean.TRUE;
}
}catch (ClassNotFoundException | SQLException e) {
log.error("---程序報錯---:{}", e.getMessage());
}
return Boolean.FALSE;
}
/**
* 校驗(yàn)數(shù)據(jù)源是否存在
* @param key 數(shù)據(jù)源保存的key
* @return 返回結(jié)果,true:存在,false:不存在
*/
public boolean existsDataSource(String key){
return Objects.nonNull(this.targetDataSourceMap.get(key));
}
}
存儲數(shù)據(jù)源信息的表
create table test_db_info(
id int auto_increment primary key not null comment '主鍵Id',
url varchar(255) not null comment '數(shù)據(jù)庫URL',
username varchar(255) not null comment '用戶名',
password varchar(255) not null comment '密碼',
driver_class_name varchar(255) not null comment '數(shù)據(jù)庫驅(qū)動'
name varchar(255) not null comment '數(shù)據(jù)庫名稱'
)
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','com.mysql.cj.jdbc.Driver','add_slave')
SpringBoot啟動時加載數(shù)據(jù)源信息
@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 = testDbInfoMapper.selectList(null);
if (CollectionUtils.isNotEmpty(testDbInfos)) {
List<DataSourceEntity> ds = new ArrayList<>();
for (TestDbInfo testDbInfo : testDbInfos) {
DataSourceEntity sourceEntity = new DataSourceEntity();
BeanUtils.copyProperties(testDbInfo,sourceEntity);
sourceEntity.setKey(testDbInfo.getName());
ds.add(sourceEntity);
}
dynamicDataSource.createDataSource(ds);
}
}
}
到了這里,關(guān)于SpringBoot整合Druid數(shù)據(jù)庫連接池&多數(shù)據(jù)源&注解切換&動態(tài)添加的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!