想到工作上可能会用到多数据源,但是自己在这方面并不是很熟悉,于是在网上查阅了很多文章,结果发现,网上的文章要么版本太老有些过时,要么用的不是mybatis-plus而是mybaits,要么步骤繁琐、需要自己手动编写aop切面代码,要么在同一service层方法中只能使用@Transactional实现单个数据源的事务管理控制,总是觉得有点不太完美,所以综合了以上文章的不足之处和可以借鉴之处,在这里总结出了一个万能的、可以适应多方面需求的多数据源配置方法,以满足相对完整的、代码简练的分布式事务控制需求。现在分享给大家。
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.20version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.22version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jta-atomikosartifactId>
dependency>
dependencies>
以上除了一些必须的、通用的依赖之外,还引入了atomikos依赖包,这是实现分布式事务管理的关键。
这个配置是可以很灵活的,数据源配置都是自定义字段,到时候方便能够在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: SELCET 1
因为我们把数据源相关的自动配置给关闭了,所以druid的自动配置项写在这里已经是无效的了,包括druid的监控功能在这里配置是不好使的。
因为我们现在要使用的是多个数据源,所以在这些地方和单数据源配置相比,略微不同。
dao层结构如以下图:
因为这里模拟的两个数据源中这张表的结构都是相同的,所以实体类就共用同一个了。注意两个数据源表的实体类是可以放在同一个文件夹中的,但是每个数据源的mapper接口类和mapper.xml文件务必放在不同的文件夹中,比如本项目分别用了master和slave两个文件夹分别存放不同数据源的mapper接口和xml文件,这是实现数据源切换的关键。
贴出代码:
@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;
}
@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();
}
@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();
}
<mapper namespace="cn.onesdream.dao.mapper.master.MasterUserMapper">
<delete id="delUser" parameterType="java.lang.String">
delete from user_test where nikename = "呦呦鹿鸣"
delete>
mapper>
<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中不建议使用*通配符,这里使用只是为了示范测试省事。
这里需要建立两个手动配置类分别对应两个数据源,两个以上数据源以此类推
@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);
}
}
@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);
}
}
以上的配置有几个需要注意的点:
到了这一步,意味着多数据源的配置已经全部完成了,接下来就可以测试并正式使用了。
以下测试代码可以参考下(我这里省略了Service层,只是测试下简单业务),整个完整demo项目包地址会在文末提供。
@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项目地址:项目地址
如有纰漏,不吝指教。