使用阿里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)); } 复制代码
结果如下
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}} 复制代码
至此,看似大功告成,实则不然,随便定义一个组建
@Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private AccountService accountService; // 方法略 } 复制代码
发现,出现循环引用的问题.
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 └─────┘ 复制代码
分析源码,不难发现"肇事者"
/** * 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); } } 复制代码