本文源代碼主要來源于簡書作者“險(xiǎn)遠(yuǎn)的奇?zhèn)ピ幑帧钡奈恼?a href="http://www.reibang.com/p/8813ec02926a" target="_blank">Spring Boot 集成Mybatis實(shí)現(xiàn)主從(多數(shù)據(jù)源)分離方案案腺,感謝作者的奉獻(xiàn)枉疼,但是由于作者寫的代碼pull下來后未能實(shí)現(xiàn)主從分離垮卓,而且有諸多bug存在,遂寫該文記錄一下。
如下圖原作者評(píng)論區(qū),大部分讀者都未實(shí)現(xiàn)主從分離:
通過debug發(fā)現(xiàn)误证,啟動(dòng)后程序并沒有進(jìn)入MybatisConfiguration中的sqlSessionFactory的方法,所有主從配置未初始化到spring容器中修壕。究其原因愈捅,是因?yàn)樽髡叨x的方法和父類的方法名返回值都一模一樣,程序走了父類MybatisAutoConfiguration中sqlSessionFactory的方法默認(rèn)的配置慈鸠,所有從庫未生效蓝谨。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
factory.setConfiguration(properties.getConfiguration());
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
找到問題后,修改如下
@Configuration
@AutoConfigureAfter({ DataSourceConfiguration.class })
public class MybatisConfiguration extends MybatisAutoConfiguration {
private static Log logger = LogFactory.getLog(MybatisConfiguration.class);
@Resource(name = "masterDataSource")
private DataSource masterDataSource;
@Resource(name = "slaveDataSource")
private DataSource slaveDataSource;
@Bean
public SqlSessionFactory sqlSessionFactoryTest() throws Exception {
return super.sqlSessionFactory(roundRobinDataSouceProxy());
}
public ReadWriteSplitRoutingDataSource roundRobinDataSouceProxy() {
ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
Map<Object, Object> targetDataResources = new ClassLoaderRepository.SoftHashMap();
// Map<Object, Object> targetDataResources = new HashMap<Object, Object>();
targetDataResources.put(ReadWriteSplitRoutingDataSource.DbType.MASTER, masterDataSource);
targetDataResources.put(ReadWriteSplitRoutingDataSource.DbType.SLAVE, slaveDataSource);
proxy.setDefaultTargetDataSource(masterDataSource);
proxy.setTargetDataSources(targetDataResources);
return proxy;
}
}
啟動(dòng)不報(bào)錯(cuò)了,但是訪問http://localhost:8080/dictype/all譬巫,報(bào)下面異常
java.lang.IllegalArgumentException: DataSource router not initialized
dataSource未初始化咖楣,debug發(fā)現(xiàn),源碼類AbstractRoutingDataSource中初始化DataSource發(fā)現(xiàn)芦昔,傳遞給父類的datasource需要是DataSource類型或者String類型诱贿,而我們?cè)谧远x類MybatisConfiguration中封裝DataSource的時(shí)候使用的是SoftHashMap
public static class SoftHashMap extends AbstractMap {
private Map<Object, SpecialValue> map;
boolean recordMiss = true; // only interested in recording miss stats sometimes
private ReferenceQueue rq = new ReferenceQueue();
public SoftHashMap(Map<Object, SpecialValue> map) {
this.map = map;
}
public SoftHashMap() {
this(new HashMap());
}
觀察SoftHashMap源碼發(fā)現(xiàn):SoftHashMap的value值類型為SpecialValue類型,不是上面源碼需要的DataSource類型或者String類型咕缎,所以修改如下:
@Configuration
@AutoConfigureAfter({ DataSourceConfiguration.class })
public class MybatisConfiguration extends MybatisAutoConfiguration {
private static Log logger = LogFactory.getLog(MybatisConfiguration.class);
@Resource(name = "masterDataSource")
private DataSource masterDataSource;
@Resource(name = "slaveDataSource")
private DataSource slaveDataSource;
@Bean
public SqlSessionFactory sqlSessionFactoryTest(ReadWriteSplitRoutingDataSource dataSource) throws Exception {
logger.info("-------------------- 重載父類 sqlSessionFactory init---------------------");
return super.sqlSessionFactory(dataSource);
}
@Bean
public ReadWriteSplitRoutingDataSource roundRobinDataSouceProxy() {
ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
Map<Object, Object> targetDataResources = new HashMap<Object, Object>();
targetDataResources.put(ReadWriteSplitRoutingDataSource.DbType.MASTER, masterDataSource);
targetDataResources.put(ReadWriteSplitRoutingDataSource.DbType.SLAVE, slaveDataSource);
proxy.setDefaultTargetDataSource(masterDataSource);
proxy.setTargetDataSources(targetDataResources);
return proxy;
}
}
重啟后再次訪問http://localhost:8080/dictype/all珠十,發(fā)現(xiàn)完美的調(diào)用了從庫的地址:
init datasource error, url: jdbc:mysql://192.168.249.128:3381/db-test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
至此,完美實(shí)現(xiàn)多數(shù)據(jù)源主從分離凭豪。
綜上焙蹭,遇到問題要多debug,多方位考慮問題的癥結(jié)所在。