需求介绍:
要求做一个平台,有其他第三方系统接入;每个系统有自己的数据源配置,通过调用平台接口,实现将数据保存到第三方自己的数据库中;
实现过程:
1.在平台项目运行时,通过接口获取每个第三方系统的数据源;以key-value的形式保存到全局变量中;
2.在调用接口的时候,会通过拦截器获取到每个请求中的第三方系统标识;
3.根据标识来切换对应的数据源文章来源:https://www.toymoban.com/news/detail-604409.html
一、提供一个公共类,保存第三方系统的数据源信息
public class ThreadSystemDataSourceInfo {
//数据源类型:mongodb, mysql, es
private String dataType;
//连接
private String url;
//用户名
private String username;
//密码
private String password;
//用来作为key存储租户配置
private String datasourceId;
}
二、提供一个接口,用来模拟查询第三方系统的数据源
public List<ThreadSystemDataSourceInfo> listDataSourceInfo() {
List<ThreadSystemDataSourceInfo> 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;
}
三、系统的开发
1、配置yml,系统要配置自己的数据库
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 #打开驼峰
2、创建数据源对象ThreadSystemDataSource
数据库连接池用的是kicari文章来源地址https://www.toymoban.com/news/detail-604409.html
public class ThreadSystemDataSource extends AbstractRoutingDataSource {
private boolean debug = true;
private final Logger log = LoggerFactory.getLogger(getClass());
private Map<Object, Object> targetDataSources;
private Object dynamicDefaultTargetDataSource;
private static final String defaultClassDriver = "com.mysql.cj.jdbc.Driver";
@Override
protected Object determineCurrentLookupKey() {
String datasourceId = MysqlContextHolder.getDataSourceId();
if (!StringUtils.isEmpty(datasourceId)) {
Map<Object, Object> dynamicTargetDataSources2 = this.targetDataSources;
if (dynamicTargetDataSources2.containsKey(datasourceId)) {
log.info("---当前数据源:" + datasourceId + "---");
} else {
log.info("不存在的数据源:");
return null;
// throw new ADIException("不存在的数据源:"+datasource,500);
}
} else {
log.info("---当前数据源:默认数据源---");
}
return datasourceId;
}
// 创建数据源
public boolean createDataSource(String key, String url, String username, String password) {
try {
String driveClass = this.defaultClassDriver;
if (!testDatasource(url, username, password)) {
log.error("数据源配置有错误,url:{},username:{},password:{}", url, username, password);
return false;
}
HikariConfig hikariConfig = new HikariConfig();
// 连接池连接信息
// hikariConfig.setMinimumIdle(mininum);
// hikariConfig.setMaximumPoolSize(maximum);
// hikariConfig.setMaxLifetime(lifeTime);
// hikariConfig.setConnectionTimeout(connectionTimeOut);
// hikariConfig.setValidationTimeout(validationTimeOut);
// hikariConfig.setIdleTimeout(idleTimeOut);
// hikariConfig.setPoolName(poolName);
// hikariConfig.setConnectionTestQuery(testQuery);
// 基础连接信息
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setDriverClassName(driveClass);
HikariDataSource datasource = new HikariDataSource(hikariConfig);
this.targetDataSources.put(key, datasource);
// 将map赋值给父类的TargetDataSources
setTargetDataSources(this.targetDataSources);
// 将TargetDataSources中的连接信息放入resolvedDataSources管理
super.afterPropertiesSet();
log.info(key + "数据源初始化成功");
return true;
} catch (Exception e) {
log.error(e + "");
return false;
}
}
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
this.targetDataSources = targetDataSources;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public boolean testDatasource(String url, String username, String password) {
try {
String driveClass = this.defaultClassDriver;
Class.forName(driveClass);
DriverManager.getConnection(url, username, password);
return true;
} catch (Exception e) {
return false;
}
}
private void createDataSource(ThreadSystemDataSourceInfo dataSource) {
String datasourceId = dataSource.getDatasourceId();
log.info("准备创建数据源" + datasourceId);
String username = dataSource.getUsername();
String password = dataSource.getPassword();
String url = dataSource.getUrl();
boolean result = this.createDataSource(datasourceId, url, username, password);
if (!result) {
log.error("数据源" + datasourceId + "配置正确,但是创建失败");
}
}
}
3、创建配置类
@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<Object, Object> targetDataSources = new HashMap<Object, Object>();
//额外数据源配置 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();
}
}
4、上下文工具类
public class MysqlContextHolder {
private final static Logger log = LoggerFactory.getLogger(MysqlContextHolder.class);
// 对当前线程的操作-线程安全的
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 调用此方法,切换数据源
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("已切换到主数据源");
}
}
5、存放数据源的工具类
public class ThreadSystemDataSourceLocalMapUtil {
//存放初始化各个租户,保存的配置信息
private static final Map<String, ThreadSystemDataSourceInfo> dataSourceInfoMap = new ConcurrentHashMap<>();
public static Map<String, ThreadSystemDataSourceInfo> 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;
}
}
6、初始化第三方系统的数据源
@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<ThreadSystemDataSourceInfo> 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);
}
}
}
}
7、开始使用
@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();
}
}
到了这里,关于springboot,多数据源切换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!