博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring boot druid多数据源及循环引用的解决策略
阅读量:6849 次
发布时间:2019-06-26

本文共 11510 字,大约阅读时间需要 38 分钟。

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

引入starter

  • 1.maven
com.alibaba
druid-spring-boot-starter
1.1.17
复制代码
  • 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=1spring.datasource.druid.connection-init-sqls=set names utf8mb4;spring.datasource.druid.test-while-idle=truespring.datasource.druid.test-on-borrow=falsespring.datasource.druid.test-on-return=falsespring.datasource.druid.use-global-data-source-stat=truespring.datasource.druid.filter.wall.config.multi-statement-allow=truespring.datasource.druid.filter.stat.log-slow-sql=truespring.datasource.druid.filter.stat.slow-sql-millis=1000spring.datasource.druid.filter.slf4j.enabled=truespring.datasource.druid.filter.slf4j.connection-log-enabled=truespring.datasource.druid.filter.slf4j.statement-executable-sql-log-enable=truespring.datasource.druid.filter.slf4j.result-set-log-enabled=falsespring.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=10spring.datasource.druid.databusiness.url=jdbc:mysql://localhost/data_business_support?useUnicode=true&characterEncoding=utf8&useSSL=false&socketTimeout=5000spring.datasource.druid.databusiness.username=rootspring.datasource.druid.databusiness.password=mysql# lismproducer数据源配置data-sources.#spring.datasource.druid.ads.maxActive=5ß#spring.datasource.druid.ads.maxOpenPreparedStatements=5spring.datasource.druid.lismproducer.url=jdbc:mysql://localhost/lismproducer?useUnicode=true&characterEncoding=utf8&useSSL=falsespring.datasource.druid.lismproducer.username=rootspring.datasource.druid.lismproducer.password=mysql复制代码
  • 2.java 配置
@Slf4j@Configurationpublic 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
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
> DATA_SOURCE_KEY = new InheritableThreadLocal<>(); public static void setDataSourceKey(String dataSource) { Stack
stack = DATA_SOURCE_KEY.get(); if (stack == null) { stack = new Stack<>(); DATA_SOURCE_KEY.set(stack); } stack.push(dataSource); } public static void cleanDataSourceKey() { Stack
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
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
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@Slf4jpublic 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);    }}复制代码

使用注解

@Servicepublic 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
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}}复制代码

问题初现

至此,看似大功告成,实则不然,随便定义一个组建

@Servicepublic 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}). *

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
targetDataSources) { this.targetDataSources = targetDataSources; } /** * Specify the default target DataSource, if any. *

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}). *

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复制代码
/** * 
 *  解决druid循环依赖问题 *  1.启动时排除DataSourceAutoConfiguration *  2.用import以全路径的形式注入bean * 
*/@Import(DataSourcesConfig.class)@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableDiscoveryClient@EnableFeignClientspublic class DataBusinessApplication { public static void main(String[] args) { SpringApplication.run(DataBusinessApplication.class, args); }}复制代码

转载于:https://juejin.im/post/5cffb40fe51d4556da53d092

你可能感兴趣的文章
kvm命令学习记录
查看>>
小菜鸡进阶之路-First week
查看>>
ORACLE 10g SYSAUX表空间快速增长之WRH$_ACTIVE_SESSION_HISTORY篇
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
mybatis大数据提交和更新,数据SQL语句批量提交数据库
查看>>
每天一个小算法(Shell Sort2)
查看>>
Java数据结构与算法(7) - ch05双向链表(Double List)
查看>>
自适应布局
查看>>
L2-015. 互评成绩
查看>>
poj3186 poj3267
查看>>
L365
查看>>
js中with、this的用法
查看>>
子数组的和的最大值(包括升级版的首尾相连数组)
查看>>
LeetCode - Nth Highest Salary
查看>>
9.ORM数据访问
查看>>
一言不合就动手系列篇一-仿电商平台前端搜索插件(filterMore)
查看>>
Oracle Split 函数
查看>>
目标跟踪之卡尔曼滤波---理解Kalman滤波的使用预测
查看>>
Git安装和基本使用(1)
查看>>