SpringBoot系列(13): 解鎖Mybatis多數據源的最簡姿勢


作者: 修羅debug
版權聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權協(xié)議,轉載請附上原文出處鏈接和本聲明。

摘要:正常情況下,一個應用一個數據庫是標配,也是很多小伙伴在開發(fā)企業(yè)級應用時最為常見的做法;然而,出于某些特殊的情況,一個應用需要跨數據庫實現(xiàn)不同的功能需求 也逐漸變得很普遍!本文,我們將分享介紹一種最為簡單的、基于注解式(Java Config)的方式實現(xiàn)Mybatis多數據源的訪問。

內容:所謂多數據源,說白了,其實就是“在應用系統(tǒng)中訪問多個數據庫實現(xiàn)某些業(yè)務功能”的另一種詮釋!目前一些開源的項目也采用了各種不同的方式實現(xiàn)了“多數據源”,常見的包括“基于Mybatis和Spring JDBC Tempalte數據源 混合實現(xiàn)多數據源”、“基于Mybatis和Spring AOP實現(xiàn)多數據源”;

第一種方式畢竟混合了兩種實現(xiàn)方式,對于咱們程序員來講,就意味著你既需要掌握Mybatis、也需要掌握Spring JDBC Template的編碼實現(xiàn)DAO層的操作、訪問;

對于第二種方式,有些小伙伴看到AOP可能有點退怯,而且事實上這種實現(xiàn)的過程,需要你在“訪問、操作數據庫的方法或者類上”額外添加某個注解,標明來源于那個數據源!

本文,我們將不介紹上面這兩種,而是采用一種更為簡單、便捷、純粹基于Mybatis的方式來實現(xiàn)多數據源,實戰(zhàn)完成過后,你會發(fā)現(xiàn),這種方式實現(xiàn)起來確實很簡單,很簡潔,很爽,當然啦,一些包目錄結構的規(guī)范還是需要遵循的,不要慌,問題不大!

廢話不多講,下面,我們就進入實戰(zhàn)過程!

(1)既然是“多數據源”,那么得需要建立多個數據庫,等待被訪問。在這里,我們仍然沿用了“technology”數據庫,并新建了一個新的數據庫“sb_redis”(在里面建立了一張數據庫表 sys_config),對該數據庫表采用Mybatis逆向工程生成相應的Entity、Mapper、Mapper.xml即可。

相應的源代碼、數據庫大家檢出來一看就行了!以下為該數據庫表sys_config的DDL:

CREATE TABLE `sys_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '字典類型',
`name` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '字典名稱',
`code` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '選項編碼',
`value` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '選項取值',
`order_by` int(11) DEFAULT '1' COMMENT '排序',
`is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_type_code` (`type`,`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='字典配置表';

(2)在這里值得一提的是,“多數據源實戰(zhàn)”也有一些要求,即“主數據源Primary”只能有一個,“從數據源Second…”可以有多個,類似于數據庫集群的“Master-Slave”模式或者ZooKeeper集群的 “Leader-Follower”選舉機制。

由于我們是采用“簡單的Java Config注解式”來實現(xiàn)“Mybatis多數據源”的,故而我們需要對不同數據源所生成的Entity、Mapper、Mapper.xml放置在不同的包目錄下,目的在于“掃描”時可以檢測到相應的“數據源”對應著相應的“包目錄下的實體以及操作接口”,如下圖所示:


(3)接下來,我們在config包下基于Java Config創(chuàng)建兩個用于“注入數據源”的顯示配置,一個是DataSourcePrimary、另一個是DataSourceSecond(如果還有第三個、第四個數據源,也是照著同樣的套路新建即可)。

當然啦,在手動顯示注入數據源配置之前,需要在配置文件application.properties中加入相應數據庫的鏈接配置,如下圖所示:


(4)接著是主數據源DataSourcePrimary的顯示配置:  

/**主數據源
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/7 9:57
**/
@Configuration
@MapperScan(basePackages = "com.debug.springboot.model.mapper.primary",sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class DataSourcePrimary {

@Autowired
private Environment env;

//數據源實例配置
@Primary
@Bean(name = "primaryDataSource")
//@ConfigurationProperties(prefix = "datasource.one")
public DataSource dataSource(){
return DataSourceBuilder.create()
.driverClassName(env.getProperty("datasource.one.driver"))
.url(env.getProperty("datasource.one.url"))
.username(env.getProperty("datasource.one.username"))
.password(env.getProperty("datasource.one.password"))
.build();
}

@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception{
SqlSessionFactoryBean bean=new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/primary/*.xml"));
return bean.getObject();
}

@Primary
@Bean(name = "primaryTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception{
return new DataSourceTransactionManager(dataSource);
}

@Primary
@Bean(name = "primarySqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory)throws Exception{
return new SqlSessionTemplate(sqlSessionFactory);
}
}

從上述該代碼中,可以看出我們是一步步注入DataSource實例、SqlSessionFactory實例以及事務配置我SqlSession的操作模板實例(這些配置大家也可以采用Mybatis的XML配置文件來實現(xiàn)?。?/span>

同時,有一點需要注意,我們必須得對該數據源配置標注為“主數據源Primary,通過@Primary實現(xiàn)即可!

而且,該主數據源指定了,com.debug.springboot.model.mapper.primary 包目錄結構下的操作Mapper將操作主數據源“technology數據庫”,對應著相應的Mapper.xml,即:

new PathMatchingResourcePatternResolver().getResources("classpath:mappers/primary/*.xml")

 (5)從數據源的顯示Java Config配置跟主數據源的配置其實沒多大區(qū)別,只需要去掉@primary注解以及調整相應的Mapper和Mapper.xml所在的包目錄!完整的源代碼如下所示:  

/**從數據源
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/7 9:57
**/
@Configuration
@MapperScan(basePackages = "com.debug.springboot.model.mapper.second",sqlSessionTemplateRef = "secondSqlSessionTemplate")
public class DataSourceSecond {
@Autowired
private Environment env;

@Bean(name = "secondDataSource")
public DataSource dataSource(){
return DataSourceBuilder.create()
.driverClassName(env.getProperty("datasource.two.driver"))
.url(env.getProperty("datasource.two.url"))
.username(env.getProperty("datasource.two.username"))
.password(env.getProperty("datasource.two.password"))
.build();
}

@Bean(name = "secondSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception{
SqlSessionFactoryBean bean=new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/second/*.xml"));
return bean.getObject();
}

@Bean(name = "secondTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("secondDataSource") DataSource dataSource) throws Exception{
return new DataSourceTransactionManager(dataSource);
}

@Bean(name = "secondSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory)throws Exception{
return new SqlSessionTemplate(sqlSessionFactory);
}
}

至此,我們該做的準備工作都做完了,下面我們進入測試環(huán)境。在controller包下建一個controller,并寫一個請求方法,用于同時訪問主數據源以及從數據源的數據,其源代碼如下所示:  

/**
* 多數據源controller
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/7 10:37
**/
@RestController
@RequestMapping("multipart/source")
public class MultipartSourceController {

private static final Logger log= LoggerFactory.getLogger(MultipartSourceController.class);

//TODO:從數據源的
@Autowired
private SysConfigMapper sysConfigMapper;

//TODO:主數據源的
@Autowired
private UserMapper userMapper;

@RequestMapping(value = "list",method = RequestMethod.GET)
public BaseResponse list(){
BaseResponse response=new BaseResponse(StatusCode.Success);
Map<String,Object> resMap= Maps.newHashMap();
try {
resMap.put("主數據源",userMapper.selectByPrimaryKey(11));
resMap.put("從數據源",sysConfigMapper.selectActiveConfigs());

}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
response.setData(resMap);
return response;
}
}

之后,在Postman發(fā)起對應的請求,即可看到相應的響應結果!


看到結果,大家會發(fā)現(xiàn)沒啥問題,確實實現(xiàn)了“主、從數據源”的多庫訪問,整個過程下來,大家會發(fā)現(xiàn)這只需要N個DataSource的顯示注入即可實現(xiàn)(N=數據庫的個數)!

而且,這種多數據源的實現(xiàn)方式還可以很好的支持“多數據源-跨庫事務”,即在一個方法里面,如果需要同時執(zhí)行“更新”N個數據庫的操作,加了@Transactional(rollbackFor = Exception.class) 注解后,是可以實現(xiàn)“事務回滾的”,如下所示:

//TODO:多數據源時-跨事務
@RequestMapping(value = "add",method = RequestMethod.POST)
@Transactional(rollbackFor = Exception.class)
public BaseResponse add(@RequestParam String name) throws Exception{
BaseResponse response=new BaseResponse(StatusCode.Success);

User user=new User();
user.setName(name);
user.setCode("100");
userMapper.insertSelective(user);

SysConfig config=new SysConfig();
config.setName(name);
//有些字典必填,故而插入時會報錯,測試是否會回滾
sysConfigMapper.insertSelective(config);

return response;
}

好了,本篇文章我們就介紹到這里了,其他相關的技術,感興趣的小伙伴可以關注底部Debug的技術公眾號,或者加Debug的微信,拉你進“微信版”的真正技術交流群!一起學習、共同成長!

補充

1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已將本文所涉及的內容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:https://www.fightjava.com/web/index/course/detail/5

3、關注一下Debug的技術微信公眾號,最新的技術文章、課程以及技術專欄將會第一時間在公眾號發(fā)布哦!