转载

spring boot druid多数据源及循环引用的解决策略

使用阿里druid及封装的druid-spring-boot-starter实现多数据源时遇到
    了一点小问题,过程虽然曲折,幸运的是最终解决了问题,特此拿来和诸君分享,第
    一次写文章,水平有限,望多多海涵。
    分享一首诗词。
    我本渔樵孟诸野,一生自是悠悠者。
    乍可狂歌草泽中,宁堪作吏风尘下?
    只言小邑无所为,公门百事皆有期。
    拜迎长官心欲碎,鞭挞黎庶令人悲。
    归来向家问妻子,举家尽笑今如此。
    生事应须南亩田,世情尽付东流水。
    梦想旧山安在哉,为衔君命且迟回。
    乃知梅福徒为尔,转忆陶潜归去来。
复制代码

引入starter

  • 1.maven
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
复制代码
  • 2.gradle
compile group: 'com.alibaba', name: 'druid-spring-boot-starter', version: '1.1.17'
复制代码

配置

  • 1.属性文件
# ==================== 数据源共通配置 开始 ====================
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
复制代码
  • 2.java 配置
@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);
    }

}
复制代码
原文  https://juejin.im/post/5cffb40fe51d4556da53d092
正文到此结束
Loading...