使用阿里druid及封裝的druid-spring-boot-starter實現多數據源時遇到
了一點小問題,過程雖然曲折,幸運的是最終解決了問題,特此拿來和諸君分享,第
一次寫文章,水平有限,望多多海涵。
分享一首詩詞。
我本漁樵孟諸野,一輩子自是悠悠者。
乍可狂歌草澤中,寧堪做吏風塵下?
只言小邑無所爲,公門百事皆有期。
拜迎長官心欲碎,鞭撻黎庶使人悲。
歸來向家問妻子,舉家盡笑今如此。
生事應須南畝田,世情盡付東流水。
夢想舊山安在哉,爲銜君命且遲迴。
乃知梅福徒爲爾,轉憶陶潛歸去來。
複製代碼
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
複製代碼
compile group: 'com.alibaba', name: 'druid-spring-boot-starter', version: '1.1.17'
複製代碼
# ==================== 數據源共通配置 開始 ====================
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.druid.initial-size=1
#spring.datasource.druid.min-idle=1
spring.datasource.druid.connection-init-sqls=set names utf8mb4;
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.use-global-data-source-stat=true
spring.datasource.druid.filter.wall.config.multi-statement-allow=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=1000
spring.datasource.druid.filter.slf4j.enabled=true
spring.datasource.druid.filter.slf4j.connection-log-enabled=true
spring.datasource.druid.filter.slf4j.statement-executable-sql-log-enable=true
spring.datasource.druid.filter.slf4j.result-set-log-enabled=false
spring.datasource.min-evictable-idle-time-millis=1800000
# 否啓用StatViewServlet(監控頁面)默認值爲false,推薦不開啓,由於不安全
spring.datasource.druid.stat-view-servlet.enabled=true
# ==================== 數據源共通配置 結束 ====================
# data_business_support數據源配置
#spring.datasource.druid.rds.maxActive=10
#spring.datasource.druid.rds.maxOpenPreparedStatements=10
spring.datasource.druid.databusiness.url=jdbc:mysql://localhost/data_business_support?useUnicode=true&characterEncoding=utf8&useSSL=false&socketTimeout=5000
spring.datasource.druid.databusiness.username=root
spring.datasource.druid.databusiness.password=mysql
# lismproducer數據源配置data-sources.
#spring.datasource.druid.ads.maxActive=5ß
#spring.datasource.druid.ads.maxOpenPreparedStatements=5
spring.datasource.druid.lismproducer.url=jdbc:mysql://localhost/lismproducer?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.druid.lismproducer.username=root
spring.datasource.druid.lismproducer.password=mysql
複製代碼
@Slf4j
@Configuration
public class DataSourcesConfig {
/**
* 主數據源
* @return
*/
@Bean(initMethod = "init", destroyMethod = "close")
@ConfigurationProperties("spring.datasource.druid.databusiness")
public DruidDataSource databusinessDataSource() {
log.debug("databusiness druid data-source init...");
return DruidDataSourceBuilder.create().build();
}
/**
* 從數據源
* @return
*/
@Bean(initMethod = "init", destroyMethod = "close")
@ConfigurationProperties("spring.datasource.druid.lismproducer")
public DruidDataSource lismProducerDataSource() {
log.debug("lismProducerDataSource druid data-source init...");
return DruidDataSourceBuilder.create().build();
}
/**
* 動態數據源
* @param databusinessDataSource databusinessDataSource
* @param lismProducerDataSource lismProducerDataSource
* @return {@link DynamicDataSource}
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("databusinessDataSource") DataSource databusinessDataSource, @Qualifier("lismProducerDataSource") DataSource lismProducerDataSource) {
Map<String, DataSource> pool = Maps.newHashMapWithExpectedSize(2);
pool.put(DataSourceTypeConstant.MASTER, databusinessDataSource);
pool.put(DataSourceTypeConstant.FIRST_FOLLOWER, lismProducerDataSource);
return new DynamicDataSource(pool, databusinessDataSource);
}
}
複製代碼
public interface DataSourceTypeConstant {
/** * 主數據源 */
String MASTER = "MASTER";
/** * 第1從數據源 */
String FIRST_FOLLOWER = "FIRST_FOLLOWER";
/** * 第2從數據源 */
String SECOND_FOLLOWER = "SECOND_FOLLOWER";
}
複製代碼
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<Stack<String>> DATA_SOURCE_KEY = new InheritableThreadLocal<>();
public static void setDataSourceKey(String dataSource) {
Stack<String> stack = DATA_SOURCE_KEY.get();
if (stack == null) {
stack = new Stack<>();
DATA_SOURCE_KEY.set(stack);
}
stack.push(dataSource);
}
public static void cleanDataSourceKey() {
Stack<String> stack = DATA_SOURCE_KEY.get();
if (stack != null) {
stack.pop();
if (stack.isEmpty()) {
DATA_SOURCE_KEY.remove();
}
}
}
/** * 構造 * * @param targetDataSources */
@SuppressWarnings({"rawtypes", "unchecked"})
public DynamicDataSource(Map<String, DataSource> targetDataSources, DataSource defaultDataSource) {
super.setTargetDataSources((Map) targetDataSources);
super.setDefaultTargetDataSource(defaultDataSource);
}
/** * determineCurrentLookupKey * * @return * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */
@Override
protected Object determineCurrentLookupKey() {
Stack<String> stack = DATA_SOURCE_KEY.get();
if (stack != null) {
return stack.peek();
}
return null;
}
}
複製代碼
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SpecDatasource {
String value();
}
複製代碼
@Component
@Aspect
@Slf4j
public class TypeSwitchDatasourceAspectJ {
@Pointcut("@annotation(com.lixcx.dataBusiness.config.multidb.SpecDatasource)")
public void pointcut() {
System.out.println("-----");
}
@Before(value="pointcut() && @annotation(specDatasource)", argNames="specDatasource")
public void before(SpecDatasource specDatasource) {
String dataSource = specDatasource.value();
if (StringUtils.isNotBlank(dataSource)) {
log.info("從主數據源->切換到->從數據源({})", dataSource);
DynamicDataSource.setDataSourceKey(dataSource);
}
}
@After("pointcut()")
public void after() {
DynamicDataSource.cleanDataSourceKey();
log.info("恢復主數據源");
DynamicDataSource.setDataSourceKey(DataSourceTypeConstant.MASTER);
}
}
複製代碼
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
@SpecDatasource(DataSourceTypeConstant.FIRST_FOLLOWER)
public Account getByAccountName(String accountName) {
return accountDao.getByAccountName(accountName);
}
}
複製代碼
@Autowired
private DruidDataSource databusinessDataSource;
@Autowired
private DruidDataSource lismProducerDataSource;
@Test
public void testMultiDb() {
List<TerminalType> terminalTypeList = terminalTypeDao.findAll();
System.out.println("--terminalTypeList->:" + JSON.toJSONString(terminalTypeList));
Account account = accountService.getByAccountName("13915263168");
System.out.println("--account->:" + JSON.toJSONString(account));
}
複製代碼
結果以下java
Hibernate: select terminalty0_.id as id1_4_, terminalty0_.flag as flag2_4_, terminalty0_.terminal_type_code as terminal3_4_, terminalty0_.terminal_type_name as terminal4_4_ from terminal_type terminalty0_
--terminalTypeList->:[{"flag":1,"id":1,"terminalTypeCode":"1","terminalTypeName":"電踏車"}]
2019-06-11 21:39:53.188 INFO 2013 --- [ main] c.l.d.c.m.TypeSwitchDatasourceAspectJ : 從主數據源->切換到->從數據源(FIRST_FOLLOWER)
Hibernate: select account0_.account_id as account_1_5_, account0_.account_category as account_2_5_, account0_.account_name as account_3_5_, account0_.account_state as account_4_5_, account0_.city_code as city_cod5_5_, account0_.create_date as create_d6_5_, account0_.password as password7_5_, account0_.user_info_id as user_inf8_5_ from account account0_ where account0_.account_name=?
Hibernate: select userinfo0_.user_id as user_id1_6_0_, userinfo0_.idcard as idcard2_6_0_, userinfo0_.nick_name as nick_nam3_6_0_, userinfo0_.phone as phone4_6_0_, userinfo0_.sex as sex5_6_0_, userinfo0_.update_date as update_d6_6_0_, userinfo0_.user_describe as user_des7_6_0_, userinfo0_.user_icon as user_ico8_6_0_ from user_info userinfo0_ where userinfo0_.user_id=?
2019-06-11 21:39:53.255 INFO 2013 --- [ main] c.l.d.c.m.TypeSwitchDatasourceAspectJ : 恢復主數據源
--account->:{"accountCategory":1,"accountId":1602,"accountName":"13915263168","accountState":1,"createDate":1559326246000,"password":"$2a$10$oQv6lM11fQfExpKOBbg2G.li9mwxy4xuQJyC6t1ruIoN3Y7w8HIt.","userInfo":{"nickName":"13915263168","phone":"13915263168","sex":"男","updateDate":1559326246000,"userDescribe":"","userIcon":"","userId":1572}}
複製代碼
至此,看似大功告成,實則否則,隨便定義一個組建
mysql
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AccountService accountService;
// 方法略
}
複製代碼
發現,出現循環引用的問題.spring
accountServiceImpl (field private com.lixcx.dataBusiness.dao.lismProducer.AccountDao com.lixcx.dataBusiness.service.lismProducer.impl.AccountServiceImpl.accountDao)
↓
accountDao
↓
(inner bean)#12942633
↓
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
| dataSource defined in class path resource [com/lixcx/dataBusiness/config/multidb/DataSourcesConfig.class]
↑ ↓
| databusinessDataSource defined in class path resource [com/lixcx/dataBusiness/config/multidb/DataSourcesConfig.class]
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘
複製代碼
分析源碼,不難發現"肇事者"sql
/** * Specify the map of target DataSources, with the lookup key as key. * The mapped value can either be a corresponding {@link javax.sql.DataSource} * instance or a data source name String (to be resolved via a * {@link #setDataSourceLookup DataSourceLookup}). * <p>The key can be of arbitrary type; this class implements the * generic lookup process only. The concrete key representation will * be handled by {@link #resolveSpecifiedLookupKey(Object)} and * {@link #determineCurrentLookupKey()}. */
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
/** * Specify the default target DataSource, if any. * <p>The mapped value can either be a corresponding {@link javax.sql.DataSource} * instance or a data source name String (to be resolved via a * {@link #setDataSourceLookup DataSourceLookup}). * <p>This DataSource will be used as target if none of the keyed * {@link #setTargetDataSources targetDataSources} match the * {@link #determineCurrentLookupKey()} current lookup key. */
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
複製代碼
解決druid循環依賴問題
* 1.啓動時排除DataSourceAutoConfiguration
* 2.用import以全路徑的形式注入bean
複製代碼
/** * <pre> * 解決druid循環依賴問題 * 1.啓動時排除DataSourceAutoConfiguration * 2.用import以全路徑的形式注入bean * </pre> */
@Import(DataSourcesConfig.class)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableFeignClients
public class DataBusinessApplication {
public static void main(String[] args) {
SpringApplication.run(DataBusinessApplication.class, args);
}
}
複製代碼