需求介绍:
要求做一个平台,有其他第三方系统接入;每个系统有自己的数据源配置,通过调用平台接口,实现将数据保存到第三方自己的数据库中;
实现过程:
1.在平台项目运行时,通过接口获取每个第三方系统的数据源;以key-value的形式保存到全局变量中;
2.在调用接口的时候,会通过拦截器获取到每个请求中的第三方系统标识;
3.根据标识来切换对应的数据源
public class ThreadSystemDataSourceInfo {
//数据源类型:mongodb, mysql, es
private String dataType;
//连接
private String url;
//用户名
private String username;
//密码
private String password;
//用来作为key存储租户配置
private String datasourceId;
}
public List listDataSourceInfo() {
List list = new ArrayList<>();
ThreadSystemDataSourceInfo info = new ThreadSystemDataSourceInfo();
info.setDataType("mysql");
info.setUrl("url");
info.setUsername("root");
info.setPassword("123456");
info.setDatasourceId("1");
ThreadSystemDataSourceInfo info1 = new ThreadSystemDataSourceInfo();
info1.setDataType("mysql");
info1.setUrl("url1");
info1.setUsername("root");
info1.setPassword("123456");
info.setDatasourceId("2");
list.add(info);
list.add(info1);
return list;
}
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
hikari:
connection-test-query: SELECT 'x' FROM DUAL
pool-name: HikariPool
maximum-pool-size: 20
connection-timeout: 10000
validation-timeout: 3000
minimum-idle: 10
idle-timeout: 30000
max-lifetime: 600000
mybatis:
mapper-locations: classpath*:mapper/**/*Mapper.xml
configuration:
map-underscore-to-camel-case: true #打开驼峰
数据库连接池用的是kicari
public class ThreadSystemDataSource extends AbstractRoutingDataSource {
private boolean debug = true;
private final Logger log = LoggerFactory.getLogger(getClass());
private Map
@Configuration
public class HikariDBConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
// 连接池连接信息
@Value("${spring.datasource.hikari.maximum-pool-size}")
private int maximum;
@Value("${spring.datasource.hikari.connection-timeout}")
private int connectionTimeOut;
@Value("${spring.datasource.hikari.validation-timeout}")
private int validationTimeOut;
@Value("${spring.datasource.hikari.idle-timeout}")
private int idleTimeOut;
@Value("${spring.datasource.hikari.max-lifetime}")
private int lifeTime;
@Value("${spring.datasource.hikari.pool-name}")
private String poolName;
@Value("${spring.datasource.hikari.connection-test-query}")
private String testQuery;
/**
* 主数据源,信息从配置文件获得
* @return
* @throws SQLException
*/
@Bean
@Primary // 在同样的DataSource中,首先使用被标注的DataSource
@Qualifier("mainDataSource")
public DataSource dataSource() throws SQLException {
HikariConfig hikariConfig = new HikariConfig();
// 连接池连接信息
hikariConfig.setMaximumPoolSize(maximum);
hikariConfig.setMaxLifetime(lifeTime);
hikariConfig.setConnectionTimeout(connectionTimeOut);
hikariConfig.setValidationTimeout(validationTimeOut);
hikariConfig.setIdleTimeout(idleTimeOut);
hikariConfig.setPoolName(poolName);
hikariConfig.setConnectionTestQuery(testQuery);
// 基础连接信息
hikariConfig.setJdbcUrl(this.url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setDriverClassName(driverClassName);
HikariDataSource datasource = new HikariDataSource(hikariConfig);
return datasource;
}
/**
* 动态获取的数据源,初始化的时候,默认只有主数据源
* @return
* @throws SQLException
*/
@Bean(name = "threadSystemDataSource")
@Qualifier("threadSystemDataSource")
public ThreadSystemDataSource threadDataSource() throws SQLException {
ThreadSystemDataSource threadDataSource = new ThreadSystemDataSource();
threadDataSource.setDebug(false);
//配置缺省的数据源
// 默认数据源配置 DefaultTargetDataSource
threadDataSource.setDefaultTargetDataSource(dataSource());
Map targetDataSources = new HashMap();
//额外数据源配置 TargetDataSources
targetDataSources.put("mainDataSource", dataSource());
threadDataSource.setTargetDataSources(targetDataSources);
return tenantDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(threadDataSource());
//解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题
sqlSessionFactoryBean.setConfiguration(configuration());
// 设置mybatis的主配置文件
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
// sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
// 设置别名包
// sqlSessionFactoryBean.setTypeAliasesPackage("com.testdb.dbsource.pojo");
// 手动配置mybatis的mapper.xml资源路径,如果单纯使用注解方式,不需要配置该行
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration configuration() {
return new org.apache.ibatis.session.Configuration();
}
}
public class MysqlContextHolder {
private final static Logger log = LoggerFactory.getLogger(MysqlContextHolder.class);
// 对当前线程的操作-线程安全的
private static final ThreadLocal contextHolder = new ThreadLocal();
// 调用此方法,切换数据源
public static void setDataSourceId(String dataSource) {
contextHolder.set(dataSource);
log.info("已切换到数据源:{}",dataSource);
}
// 获取数据源
public static String getDataSourceId() {
return contextHolder.get();
}
// 删除数据源
public static void clearDataSource() {
contextHolder.remove();
log.info("已切换到主数据源");
}
}
public class ThreadSystemDataSourceLocalMapUtil {
//存放初始化各个租户,保存的配置信息
private static final Map dataSourceInfoMap = new ConcurrentHashMap<>();
public static Map getDataSourceInfoMap() {
return dataSourceInfoMap;
}
public static void putTenantDataSource(String key, ThreadSystemDataSourceInfo dataSourceInfo) {
dataSourceInfoMap.put(key, dataSourceInfo);
}
public static TenantDataSourceInfo getDataSource(String key) {
return dataSourceInfoMap.get(key);
}
public static String getTenantDataSourceType(String key){
ThreadSystemDataSourceInfo dataSourceInfo = dataSourceInfoMap.get(key);
if(dataSourceInfo!=null){
return dataSourceInfo.getDataType();
}
return null;
}
}
@Component
public class InitDataSource implements CommandLineRunner {
@Autowired
private ITenantList iTenantList;
@Autowired
private ThreadSystemDataSource tenantDataSource;
/**
* 获取租户数据源配置,创建数据源
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
//获取租户数据源配置
List dataSourceInfos = iTenantList.listDataSourceInfo();
if(CollectionUtil.isNotEmpty(dataSourceInfos)){
//初始化数据源
for (ThreadSystemDataSourceInfo info : dataSourceInfos)
tenantDataSource.createDataSource(info.getDatasourceId(), info.getUrl(), info.getUsername(), info.getPassword());
ThreadSystemDataSourceLocalMapUtil.putTenantDataSource(info.getDatasourceId(), info);
}
}
}
}
@Service
public class TenantLogServiceForMysqlImpl {
@Resource
private SystemMapper systemMapper;
public R saveOperLog(String tenantId) {
if(ThreadSystemDataSourceLocalMapUtil.getDataSource(key) == null){
//没有配置,则使用默认数据源
MysqlContextHolder.clearDataSource();
}else{
//根据tenantId作为key,来切换数据源
MysqlContextHolder.setDataSourceId(key);
}
systemMapper.insertOperlog(operLog);
return R.ok();
}
}