SpringBoot系列(13): 解锁Mybatis多数据源的最简姿势


作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

摘要:正常情况下,一个应用一个数据库是标配,也是很多小伙伴在开发企业级应用时最为常见的做法;然而,出于某些特殊的情况,一个应用需要跨数据库实现不同的功能需求 也逐渐变得很普遍!本文,我们将分享介绍一种最为简单的、基于注解式(Java Config)的方式实现Mybatis多数据源的访问。

内容:所谓多数据源,说白了,其实就是“在应用系统中访问多个数据库实现某些业务功能”的另一种诠释!目前一些开源的项目也采用了各种不同的方式实现了“多数据源”,常见的包括“基于Mybatis和Spring JDBC Tempalte数据源 混合实现多数据源”、“基于Mybatis和Spring AOP实现多数据源”;

第一种方式毕竟混合了两种实现方式,对于咱们程序员来讲,就意味着你既需要掌握Mybatis、也需要掌握Spring JDBC Template的编码实现DAO层的操作、访问;

对于第二种方式,有些小伙伴看到AOP可能有点退怯,而且事实上这种实现的过程,需要你在“访问、操作数据库的方法或者类上”额外添加某个注解,标明来源于那个数据源!

本文,我们将不介绍上面这两种,而是采用一种更为简单、便捷、纯粹基于Mybatis的方式来实现多数据源,实战完成过后,你会发现,这种方式实现起来确实很简单,很简洁,很爽,当然啦,一些包目录结构的规范还是需要遵循的,不要慌,问题不大!

废话不多讲,下面,我们就进入实战过程!

(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 '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_type_code` (`type`,`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='字典配置表';

(2)在这里值得一提的是,“多数据源实战”也有一些要求,即“主数据源Primary”只能有一个,“从数据源Second…”可以有多个,类似于数据库集群的“Master-Slave”模式或者ZooKeeper集群的 “Leader-Follower”选举机制。

由于我们是采用“简单的Java Config注解式”来实现“Mybatis多数据源”的,故而我们需要对不同数据源所生成的Entity、Mapper、Mapper.xml放置在不同的包目录下,目的在于“扫描”时可以检测到相应的“数据源”对应着相应的“包目录下的实体以及操作接口”,如下图所示:


(3)接下来,我们在config包下基于Java Config创建两个用于“注入数据源”的显示配置,一个是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配置文件来实现!)

同时,有一点需要注意,我们必须得对该数据源配置标注为“主数据源Primary,通过@Primary实现即可!

而且,该主数据源指定了,com.debug.springboot.model.mapper.primary 包目录结构下的操作Mapper将操作主数据源“technology数据库”,对应着相应的Mapper.xml,即:

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

 (5)从数据源的显示Java Config配置跟主数据源的配置其实没多大区别,只需要去掉@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);
}
}

至此,我们该做的准备工作都做完了,下面我们进入测试环境。在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发起对应的请求,即可看到相应的响应结果!


看到结果,大家会发现没啥问题,确实实现了“主、从数据源”的多库访问,整个过程下来,大家会发现这只需要N个DataSource的显示注入即可实现(N=数据库的个数)!

而且,这种多数据源的实现方式还可以很好的支持“多数据源-跨库事务”,即在一个方法里面,如果需要同时执行“更新”N个数据库的操作,加了@Transactional(rollbackFor = Exception.class) 注解后,是可以实现“事务回滚的”,如下所示:

//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的技术微信公众号,最新的技术文章、课程以及技术专栏将会第一时间在公众号发布哦!