想到工作上可能会用到多数据源,但是自己在这方面并不是很熟悉,于是在网上查阅了很多文章,结果发现,网上的文章要么版本太老有些过时,要么用的不是mybatis-plus而是mybaits,要么步骤繁琐、需要自己手动编写aop切面代码,要么在同一service层方法中只能使用@Transactional实现单个数据源的事务管理控制,总是觉得有点不太完美,所以综合了以上文章的不足之处和可以借鉴之处,在这里总结出了一个万能的、可以适应多方面需求的多数据源配置方法,以满足相对完整的、代码简练的分布式事务控制需求。现在分享给大家。
一. 从maven中导入必须用到的依赖
<dependencies>
<!-- mysql数据库依赖组件-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- mybatis-plus依赖组件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- druid连接池依赖组件-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<!-- 多数据源的分布式事务控制-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
</dependencies>
以上除了一些必须的、通用的依赖之外,还引入了atomikos依赖包,这是实现分布式事务管理的关键。
二. application.yml文件的配置
这个配置是可以很灵活的,数据源配置都是自定义字段,到时候方便能够在java代码中引用,以下是两个数据源的示范:
server:
port: 10010
spring:
autoconfigure:
#停用druid连接池的自动配置
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
#选用druid的XADataSource数据源,因为这个数据源支持分布式事务管理
type: com.alibaba.druid.pool.xa.DruidXADataSource
#以下是自定义字段
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/local_test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3307/remote_test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
validation-query: SELECT 1
因为我们把数据源相关的自动配置给关闭了,所以druid的自动配置项写在这里已经是无效的了,包括druid的监控功能在这里配置是不好使的。
三. Dao层配置
因为我们现在要使用的是多个数据源,所以在这些地方和单数据源配置相比,略微不同。
dao层结构如以下图:
因为这里模拟的两个数据源中这张表的结构都是相同的,所以实体类就共用同一个了。注意两个数据源表的实体类是可以放在同一个文件夹中的,但是每个数据源的mapper接口类和mapper.xml文件务必放在不同的文件夹中,比如本项目分别用了master和slave两个文件夹分别存放不同数据源的mapper接口和xml文件,这是实现数据源切换的关键。
贴出代码:
1.User
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_test")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String mobile;
private String password;
private String nikename;
}
2.MasterUserMapper
@Repository
public interface MasterUserMapper extends BaseMapper<User> {
@Select("select * from user_test")
List<User> listUser();
@Select("select * from user_test")
List<User> listPageUser(Page page);
@Select("insert into user_test (mobile, password, nikename) values ('13777777777', 'abc123', '呦呦鹿鸣')")
Integer addUser();
Integer delUser();
}
3.SlaveUserMapper
@Repository
public interface SlaveUserMapper extends BaseMapper<User> {
@Select("select * from user_test")
List<User> listUser();
@Select("select * from user_test")
List<User> listPageUser(Page page);
@Select("insert into user_test (mobile, password, nikename) values ('13777776666', 'abc123', '朗朗繁星')")
Integer addUser();
Integer delUser();
}
4.MasterUserMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.onesdream.dao.mapper.master.MasterUserMapper">
<delete id="delUser" parameterType="java.lang.String">
delete from user_test where nikename = "呦呦鹿鸣"
</delete>
</mapper>
5.SlavaUserMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.onesdream.dao.mapper.slave.SlaveUserMapper">
<delete id="delUser" parameterType="java.lang.String">
delete from user_test where nikename = "朗朗繁星"
</delete>
</mapper>
PS:在实际crud中不建议使用*通配符,这里使用只是为了示范测试省事。
四. 手动配置dataSource数据源类、sqlSessionFactory工厂类、sqlSessionTemplate模板类
这里需要建立两个手动配置类分别对应两个数据源,两个以上数据源以此类推
1.MasterDateSourceConfig
@Configuration
@MapperScan(basePackages = "cn.onesdream.dao.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {
@Value("${spring.datasource.dynamic.datasource.master.url}")
private String url;
@Value("${spring.datasource.dynamic.datasource.master.username}")
private String username;
@Value("${spring.datasource.dynamic.datasource.master.password}")
private String password;
@Value("${spring.datasource.dynamic.datasource.master.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.type}")
private String dataSourceClassName;
@Value("${spring.datasource.dynamic.datasource.validation-query}")
private String testQuery;
@Bean(name = "masterDataSource")
public DataSource masterDataSource() {
AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
sourceBean.setUniqueResourceName("masterDataSource");
sourceBean.setXaDataSourceClassName(dataSourceClassName);
sourceBean.setTestQuery("select 1");
sourceBean.setBorrowConnectionTimeout(3);
MysqlXADataSource dataSource = new MysqlXADataSource();
dataSource.setUser(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
sourceBean.setXaDataSource(dataSource);
return sourceBean;
}
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/master/*Mapper.xml"));
//手动设置session工厂时,需要手动添加分页插件
Interceptor[] plugins = new Interceptor[1];
plugins[0] = new PaginationInterceptor();
sqlSessionFactoryBean.setPlugins(plugins);
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "masterSqlSessionTemplate")
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
2.SlaveDataSourceConfig
@Configuration
@MapperScan(basePackages = "cn.onesdream.dao.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class SlaveDataSourceConfig {
@Value("${spring.datasource.dynamic.datasource.slave.url}")
private String url;
@Value("${spring.datasource.dynamic.datasource.slave.username}")
private String username;
@Value("${spring.datasource.dynamic.datasource.slave.password}")
private String password;
@Value("${spring.datasource.dynamic.datasource.slave.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.type}")
private String dataSourceClassName;
@Value("${spring.datasource.dynamic.datasource.validation-query}")
private String testQuery;
@Bean(name = "slaveDataSource")
public DataSource slaveDataSource() {
AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
sourceBean.setUniqueResourceName("slaveDataSource");
sourceBean.setXaDataSourceClassName(dataSourceClassName);
sourceBean.setTestQuery("select 1");
sourceBean.setBorrowConnectionTimeout(3);
MysqlXADataSource dataSource = new MysqlXADataSource();
dataSource.setUser(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
sourceBean.setXaDataSource(dataSource);
return sourceBean;
}
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/slave/*Mapper.xml"));
//手动设置session工厂时,需要手动添加分页插件
Interceptor[] plugins = new Interceptor[1];
plugins[0] = new PaginationInterceptor();
sqlSessionFactoryBean.setPlugins(plugins);
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "slaveSqlSessionTemplate")
public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
以上的配置有几个需要注意的点:
- 每个数据源对应一个配置类
- 每个配置类的@MapperScan注解不一样,各自对应自己mapper接口文件夹(这就是为什么要将不同数据源的mapper接口写在不同文件夹的原因了)
- 在配置sqlSession工厂类的时候,创建的是MybatisSqlSessionFactoryBean,是为了能够正常使用Mybatis-Plus组件的基本功能,比如通用的crud语句绑定。
- 配置工厂类的时候,需要指定各自mapper.xml存放的路径(这就是为什么要将不同数据源的mapper.xml写在不同文件夹的原因了)
- 配置工厂类的时候,需要手动将分页插件加进去。因为数据源相关的自动配置被我们关闭了,创建传统PaginationInterceptor类的方法已经不好使了。
五. 编写Controller层和Service层
到了这一步,意味着多数据源的配置已经全部完成了,接下来就可以测试并正式使用了。
以下测试代码可以参考下(我这里省略了Service层,只是测试下简单业务),整个完整demo项目包地址会在文末提供。
1. Test01Controller
@RestController
@RequestMapping("/manage/web/test01")
public class Test01Controller {
@Autowired
private MasterUserMapper masterUserMapper;
@Autowired
private SlaveUserMapper slaveUserMapper;
@ApiOperation("多数据源基础查询测试")
@GetMapping("/01")
public ResponseResult test01(){
List<User> users = null;
List<User> users1 = null;
users = masterUserMapper.listUser();
users1 = slaveUserMapper.listUser();
ArrayList<Object> list = new ArrayList<>();
list.add(users);
list.add(users1);
return new ResponseResult(list);
}
@ApiOperation("测试mybatisPlus通用crud功能")
@GetMapping("/02")
public ResponseResult test02(){
masterUserMapper.delUser();
slaveUserMapper.delUser();
User user = new User(null, "7758258", "谁知盘中餐,粒粒皆辛苦", "悯农");
masterUserMapper.insert(user);
slaveUserMapper.insert(user);
return new ResponseResult();
}
@ApiOperation("分布式事务测试")
@GetMapping("/03")
@Transactional(rollbackFor = Exception.class)
public ResponseResult test03(){
masterUserMapper.addUser();
int i = 1/0;
slaveUserMapper.addUser();
return new ResponseResult();
}
@ApiOperation("mybatis-plus通用分页和自定义分页功能测试")
@GetMapping("/04")
public ResponseResult test04(Page page){
Page page1 = masterUserMapper.selectMapsPage(page, null);
List<User> pageUser1 = slaveUserMapper.listPageUser(page);
Page page2 = slaveUserMapper.selectPage(page, null);
List<User> pageUser2 = masterUserMapper.listPageUser(page);
ArrayList<Object> list = new ArrayList<>();
list.add(page1);
list.add(pageUser1);
list.add(page2);
list.add(pageUser2);
return new ResponseResult(list);
}
}
以上用了swagger注解,方便生成开发文档。ResponseResult类是自定义的一个通用返回结果包装类。
完整demo项目地址:项目地址
如有纰漏,不吝指教。
欢迎关注博客朗朗繁星的博客