前端时间做了一个SaaS的应用,也是第一次弄SaaS相关软件。系统设计的不同租户之前是数据库级别的隔离。所以核心的内容基本就是如何实现不同之间的数据源的切换。
创建模拟数据库
创建两个数据库,分别命名为dynamic_datasource_one和dynamic_datasource_two,两个数据库内分别创建两张表,分别名称为table_one和table_two。
数据库名 | 表名 |
---|---|
dynamic_datasource_one | table_one |
dynamic_datasource_two | table_two |
创建表的Sql如下:
create table table_one
(
id bigint auto_increment
primary key,
name varchar(30) null
);
INSERT INTO table_one (id, name) VALUES (1, 'dynamic_datasource_one');
create table table_two
(
id bigint auto_increment
primary key,
name varchar(30) null
);
INSERT INTO table_two (id, name) VALUES (1, 'dynamic_datasource_two');
创建工程
创建工程就不多说了,主要是添加mybatis-plus
依赖就好了。
<!-- 添加MyBatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.1</version>
</dependency>
修改配置文件
配置两个数据库的连接信息,我这里放到配置文件中,如果是多租户的,也可以从数据库里面获取数据库连接信息。
spring:
datasource:
url: jdbc:mysql://localhost:3306/dynamic_datasource_one?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package: net.xiangcaowuyu.DynamicDataSource.doamin
mapper-locations: classpath:mapper/*Mapper.xml
添加配置文件
将yaml
中的配置信息映射到类上,方便使用。
创建MyBatisConfig.java
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
private final Logger logger = LoggerFactory.getLogger(MyBatisConfig.class);
/**
* 数据源1
* @return
*/
public DataSource dynamicDataSourceOne( ) {
return DataSourceBuilder
.create()
.url("jdbc:mysql://localhost:3306/dynamic_datasource_one?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true")
.username("root")
.password("123456")
.driverClassName("com.mysql.jdbc.Driver")
.build();
}
/**
* 数据源2
* @return
*/
public DataSource dynamicDataSourceTwo() {
return DataSourceBuilder
.create()
.url("jdbc:mysql://localhost:3306/dynamic_datasource_two?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true")
.username("root")
.password("123456")
.driverClassName("com.mysql.jdbc.Driver")
.build();
}
@Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
DataSource dynamicDataSourceOne = dynamicDataSourceOne();
DataSource dynamicDataSourceTwo = dynamicDataSourceTwo();
Map<Object,Object> dynamicDataSourceMap = new HashMap<>();
dynamicDataSourceMap.put("dynamicDataSourceOne",dynamicDataSourceOne);
dynamicDataSourceMap.put("dynamicDataSourceTwo",dynamicDataSourceTwo);
DynamicDataSourceContextHolder.addDataSourceKeys(dynamicDataSourceMap.keySet());
dynamicDataSource.setTargetDataSources(dynamicDataSourceMap);
//必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动态切换才会起效
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
创建DataSourceContextHolder
存储当前数据源
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(){
@Override
protected String initialValue(){
return "dynamicDataSourceOne";
}
};
public static List<Object> dataSourceKeyList = new ArrayList<>();
public static void setDataSourceKey(String key) throws Exception {
if(!dataSourceKeyList.contains(key)){
throw new Exception("数据库连接不存在");
}
contextHolder.set(key);
}
public static String getDataSourceKey(){
return contextHolder.get();
}
public static void clearDataSourceKey(String key){
contextHolder.remove();
}
/**
* 添加数据库连接键
* @param keys
*/
public static void addDataSourceKeys(Collection<Object> keys){
dataSourceKeyList.addAll(keys);
}
}
创建DynamicDataSource
配置每次执行sql时,调用的数据源。
/**
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 设置数据源Key值来切换数据,每次执行sql会先调用这个方法
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
测试
创建Mapper
TableOneMapper
@Mapper
public interface TableOneMapper extends BaseMapper<TableOne> {
}
TableTwoMapper
@Mapper
public interface TableTwoMapper extends BaseMapper<TableOne> {
}
创建controller
@RestController
@RequestMapping("/")
public class DynamicDatSourceController {
private final Logger logger = LoggerFactory.getLogger(DynamicDatSourceController.class);
@Resource
TableOneMapper tableOneMapper;
@Resource
TableTwoMapper tableTwoMapper;
@GetMapping
public void test() throws Exception {
logger.info("准备查询数据源1的数据");
logger.info(tableOneMapper.selectList(new QueryWrapper<>()).get(0).getName());
logger.info("准备查询数据源2的数据");
DynamicDataSourceContextHolder.setDataSourceKey("dynamicDataSourceTwo");
logger.info(tableTwoMapper.selectList(new QueryWrapper<>()).get(0).getName());
}
}
测试下,谢谢