深度排查整合shardingjdbc失效問(wèn)題

1. 背景

最近手頭上剛好需要整合mybatis-plus和shardingjdbc項(xiàng)目,那么框架是springboot,所以打算使用mybatis-plus的starter和shardingjdbc的starter。

2. 整合mybatis-plus和shardingjdbc

1.pom文件

   <!-- Mybatis-plus時(shí)引入的配置 start -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>

這里用的mybatis-plus-boot-starter版本是3.2.0,sharding-jdbc-spring-boot-starter是3.1.0

整合好了以后就把常規(guī)的mybatis-plus相關(guān)配置配一下,還有shardingjdb的配置泞当,如下:

# sharding-jdbc配置
sharding:
  jdbc:
    datasource:
      names: test1,test2
      test1: # 數(shù)據(jù)源1
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://xx:3306/xx?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: xx
        password: xx
      test2: # 數(shù)據(jù)源2
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://yyy:3306/activitycore?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: yy
        password: yy
    config:
      sharding:
        tables:
          config:
            database-strategy:
              standard:
                sharding-column: code
                # 分片規(guī)則class
                precise-algorithm-class-name: com.yy.yy.ModuloShardingDatabaseAlgorithm

# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.yyy.entity
  configuration:
    cache-enabled: false
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    ## mybatis日志上線時(shí)關(guān)閉,僅用于本地調(diào)試
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.發(fā)現(xiàn)問(wèn)題

1.配置好了以后民珍,啟動(dòng)一下零蓉,發(fā)現(xiàn)報(bào)錯(cuò)

Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
    at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.mybatis.spring.support.SqlSessionDaoSupport.checkDaoConfig(SqlSessionDaoSupport.java:122) ~[mybatis-spring-2.0.2.jar:2.0.2]
    at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:73) ~[mybatis-spring-2.0.2.jar:2.0.2]
    at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]

2.看一下代碼


image.png

就是這個(gè)東西為空?qǐng)?bào)錯(cuò)了笤受,這個(gè)sqlSessionTemplate是空的導(dǎo)致的,然后我們這邊找一下賦值的地方敌蜂。

image.png

發(fā)現(xiàn)是空的箩兽,那是在哪寫(xiě)入的值呢,這里先留個(gè)謎底章喉。

3.雖然我們不知道那里寫(xiě)入的這個(gè)值汗贫,但是我們知道肯定是要賦值這個(gè)sqlSessionTemplate的,不然就報(bào)錯(cuò)了秸脱。那么對(duì)于sqlSessionTemplate這個(gè)基本上都是放到spring的ioc去管理的落包,所以需要看一下在哪注入到spring的ioc中的。

4.這個(gè)sqlSessionTemplate很容易就讓我們想到了應(yīng)該是在mybatisplus的configuration的時(shí)候@bean寫(xiě)進(jìn)去的摊唇。那么到這個(gè)mybatis-plus的自動(dòng)裝配類(lèi):MybatisPlusAutoConfiguration

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration implements InitializingBean {
 //xxxx
// xxxx
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
}

看到了咐蝇,就是在MybatisPlusAutoConfiguration注入的,那么打上斷點(diǎn)巷查。

5.發(fā)現(xiàn)確實(shí)沒(méi)進(jìn)來(lái)有序,那么為啥沒(méi)進(jìn)來(lái),看這個(gè)上面有@ConditionalOnMissingBean注解岛请,難道是已經(jīng)注入了過(guò)了旭寿,然后我這次又在spring初始化單例bean的時(shí)候打上斷點(diǎn),發(fā)現(xiàn)并沒(méi)有初始化過(guò)名字是sqlSessionTemplate的bean崇败。

6.那么很有可能是這整個(gè)MybatisPlusAutoConfiguration都是沒(méi)加載的盅称,多打幾個(gè)斷點(diǎn),發(fā)現(xiàn)確實(shí)都沒(méi)進(jìn)入

7.那么怎么排查MybatisPlusAutoConfiguration加載情況呢后室?

8.在application.yml加上debug=true缩膝,就會(huì)打印出加載情況

image.png

9.可以看到這里是由于缺少了datasource的bean,導(dǎo)致MybatisPlusAutoConfiguration沒(méi)有注入岸霹,然后sqlSessionTemplate也沒(méi)注入逞盆。

4.解決問(wèn)題

1.那么找到原因是因?yàn)闆](méi)有datasource的bean,其實(shí)正常的業(yè)務(wù)來(lái)說(shuō)是需要配置spring.datasource.xxx來(lái)配置數(shù)據(jù)源松申,這樣就能注入了。但是由于這里接入了shardingjdbc俯逾,那么默認(rèn)的數(shù)據(jù)源就需要用shardingjdbc贸桶,所以沒(méi)配置spring.datasource.xxx
因此我們需要將shardingjdbc包裝成數(shù)據(jù)源桌肴,放到mybatisplus里面皇筛。

2.這里也是看一下shardingjdbc的starter的configuration

@Configuration
@EnableConfigurationProperties({
        SpringBootShardingRuleConfigurationProperties.class, SpringBootMasterSlaveRuleConfigurationProperties.class, 
        SpringBootConfigMapConfigurationProperties.class, SpringBootPropertiesConfigurationProperties.class
})
@RequiredArgsConstructor
public class SpringBootConfiguration implements EnvironmentAware {
    
    private final SpringBootShardingRuleConfigurationProperties shardingProperties;
    
    private final SpringBootMasterSlaveRuleConfigurationProperties masterSlaveProperties;
    
    private final SpringBootConfigMapConfigurationProperties configMapProperties;
    
    private final SpringBootPropertiesConfigurationProperties propMapProperties;
    
    private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
    
    /**
     * Get data source bean.
     *
     * @return data source bean
     * @throws SQLException SQL exception
     */
    @Bean
    public DataSource dataSource() throws SQLException {
        return null == masterSlaveProperties.getMasterDataSourceName()
                ? ShardingDataSourceFactory
            .createDataSource(dataSourceMap, shardingProperties.getShardingRuleConfiguration(), configMapProperties.getConfigMap(), propMapProperties.getProps())
                : MasterSlaveDataSourceFactory.createDataSource(
                        dataSourceMap, masterSlaveProperties.getMasterSlaveRuleConfiguration(), configMapProperties.getConfigMap(), propMapProperties.getProps());
    }
    
    @Override
    public final void setEnvironment(final Environment environment) {
        setDataSourceMap(environment);
    }
    
    @SuppressWarnings("unchecked")
    private void setDataSourceMap(final Environment environment) {
        String prefix = "sharding.jdbc.datasource.";
        String dataSources = environment.getProperty(prefix + "names");
        for (String each : dataSources.split(",")) {
            try {
                Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + each.trim(), Map.class);
                Preconditions.checkState(!dataSourceProps.isEmpty(), "Wrong datasource properties!");
                DataSource dataSource = DataSourceUtil
                    .getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);
                dataSourceMap.put(each, dataSource);
            } catch (final ReflectiveOperationException ex) {
                throw new ShardingException("Can't find datasource type!", ex);
            }
        }
    }
}

這里很簡(jiǎn)單,可以看到shardingjdbc是自動(dòng)注入了datasource了坠七,但是為啥mybatisplus的configuration沒(méi)有找到呢水醋?

3.原因就是加載的順序旗笔,在datasouce還沒(méi)注入的時(shí)候mybatis的configuration就已經(jīng)開(kāi)始加載了,自然就加載不到

4.解決辦法

@AutoConfigureAfter(SpringBootConfiguration.class)
@Configuration
public class CustomMybatisPlusAutoConfiguration  extends MybatisPlusAutoConfiguration {
    public CustomMybatisPlusAutoConfiguration(MybatisPlusProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider, ApplicationContext applicationContext) {
        super(properties, interceptorsProvider, typeHandlersProvider, languageDriversProvider, resourceLoader,
                databaseIdProvider, configurationCustomizersProvider, mybatisPlusPropertiesCustomizerProvider, applicationContext);
    }
}

MybatisPlusAutoConfiguration單獨(dú)拉出來(lái)集成拄踪,然后配置蝇恶,通過(guò)@AutoConfigureAfter讓他加載順在jdbc的SpringBootConfiguration之后。

  1. 這樣就順利解決了

5.sqlsessionTemplete的注入之謎

1.對(duì)于上面有一個(gè)點(diǎn)惶桐,sqlsessionTemplete發(fā)現(xiàn)是空的撮弧,然后找賦值的地方發(fā)現(xiàn)沒(méi)地方給賦值。

image.png

2.對(duì)于這種情況姚糊,那么首先就想到的就是這個(gè)注入是通過(guò)反射寫(xiě)入的贿衍,在反射的地方拿出這個(gè)數(shù)據(jù),然后給設(shè)置值救恨。

3.通過(guò)斷點(diǎn)發(fā)現(xiàn)是有調(diào)用的這個(gè)

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

但是這個(gè)是為啥能調(diào)用呢贸辈,難道方法名和屬性名一致,然后注入bean的時(shí)候就會(huì)自動(dòng)填充肠槽?但是這里也沒(méi)有@Autowired相關(guān)的使用

private SqlSessionTemplate sqlSessionTemplate;


  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

4.我們?cè)谡{(diào)用setSqlSessionFactory的地方加上斷點(diǎn)擎淤,然后在上幾層調(diào)用棧發(fā)現(xiàn)確實(shí)是需要填充這個(gè)sqlSessionFactory字段。


image.png

5.這里我們一路跟到源頭是哪里給這個(gè)屬性賦值的署浩,方法:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

image.png

6.當(dāng)可以看出揉燃,當(dāng)前的resolveAuotwireMode是2,也就是AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE筋栋,這里就會(huì)把這些字段加載進(jìn)去炊汤。

7.那么這個(gè)值在哪設(shè)置的呢?查看setAutowireMode方法

image.png

image.png

這里就能看到是在掃描mapper的時(shí)候弊攘,注入bean定義時(shí)設(shè)置的抢腐。

這個(gè)處理bean定義會(huì)在這個(gè)org.mybatis.spring.mapper.MapperScannerConfigurer調(diào)用。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  //xxxxx
}

看這個(gè)類(lèi)實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor就知道襟交,會(huì)在bean定義注冊(cè)后調(diào)用迈倍。

7.BeanDefinition的autowireMode

1.在上面我們提到了BeanDefinitionautowireMode
這里對(duì)這個(gè)值解釋一下:

0 AUTOWIRE_NO: 默認(rèn)裝配模式捣域, 目前非xml配置都是使用這種方式啼染,然后程序員使用注解手動(dòng)注入

1 AUTOWIRE_BY_NAME: 通過(guò)set方法,并且 set方法的名稱(chēng)需要和bean的name一致 byName

2 AUTOWIRE_BY_TYPE: 通過(guò)set方法,并且再根據(jù)bean的類(lèi)型焕梅,注入屬性迹鹅,是通過(guò)類(lèi)型配置 byType
這個(gè)有點(diǎn)像@Autowired注解,但是這個(gè)不一樣的是針對(duì)bean的所有字段贞言,這樣只要你設(shè)置了這個(gè)值斜棚,相當(dāng)于所有字段默認(rèn)會(huì)去spring ioc中填充。

2.例子:
2.1 這里ConfigServiceImpl定義了一個(gè)成員變量:userService

@Service
public class ConfigServiceImpl implements ConfigService {
    @Override
    public void add() {
    }
    private UserService userService;

    public UserService getUserService() {
        return userService;
    }

    public void setUserService(UserService UserService) {
        this.userService = UserService;
    }
}

2.2 這樣直接啟動(dòng),setUserService函數(shù)是不會(huì)被調(diào)用的弟蚀,因?yàn)锽ean定義的autowireModeAUTOWIRE_NO

2.3 那么我們?cè)囍薷囊幌耣ean的定義蚤霞,把它改成AUTOWIRE_BY_NAME

@Component
public class UpdateAutowireModeBeanFactory implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinition bd = registry.getBeanDefinition("configServiceImpl");
        if (bd == null) {
            return;
        }
        GenericBeanDefinition gbd = (GenericBeanDefinition) bd;
        gbd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        return;
    }
}

2.4 再啟動(dòng),就能看到setUserService函數(shù)被調(diào)用了义钉,并且userService有值了

image.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昧绣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子断医,更是在濱河造成了極大的恐慌滞乙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鉴嗤,死亡現(xiàn)場(chǎng)離奇詭異斩启,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)醉锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)兔簇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人硬耍,你說(shuō)我怎么就攤上這事垄琐。” “怎么了经柴?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵狸窘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坯认,道長(zhǎng)翻擒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任牛哺,我火速辦了婚禮陋气,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘引润。我一直安慰自己巩趁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布淳附。 她就那樣靜靜地躺著议慰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奴曙。 梳的紋絲不亂的頭發(fā)上别凹,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音缆毁,去河邊找鬼。 笑死到涂,一個(gè)胖子當(dāng)著我的面吹牛脊框,可吹牛的內(nèi)容都是我干的颁督。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浇雹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沉御!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起昭灵,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吠裆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后烂完,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體试疙,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年抠蚣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祝旷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘶窄,死狀恐怖怀跛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柄冲,我是刑警寧澤吻谋,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站现横,受9級(jí)特大地震影響漓拾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜长赞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一晦攒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧得哆,春花似錦脯颜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至饱亮,卻和暖如春矾芙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背近上。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工剔宪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓葱绒,卻偏偏與公主長(zhǎng)得像感帅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子地淀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容