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.看一下代碼
就是這個(gè)東西為空?qǐng)?bào)錯(cuò)了笤受,這個(gè)sqlSessionTemplate
是空的導(dǎo)致的,然后我們這邊找一下賦值的地方敌蜂。
發(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ì)打印出加載情況
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
之后。
- 這樣就順利解決了
5.sqlsessionTemplete的注入之謎
1.對(duì)于上面有一個(gè)點(diǎn)惶桐,sqlsessionTemplete
發(fā)現(xiàn)是空的撮弧,然后找賦值的地方發(fā)現(xiàn)沒(méi)地方給賦值。
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字段。
5.這里我們一路跟到源頭是哪里給這個(gè)屬性賦值的署浩,方法:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
6.當(dāng)可以看出揉燃,當(dāng)前的resolveAuotwireMode
是2,也就是AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE
筋栋,這里就會(huì)把這些字段加載進(jìn)去炊汤。
7.那么這個(gè)值在哪設(shè)置的呢?查看setAutowireMode
方法
這里就能看到是在掃描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.在上面我們提到了BeanDefinition
的autowireMode
。
這里對(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定義的autowireMode
是AUTOWIRE_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
有值了