解鎖無限可能:使用SpringBoot和MyBatis打造動態(tài)數(shù)據(jù)源
今天分享一個利用SpringBoot和Mybatis實現(xiàn)動態(tài)加載數(shù)據(jù)源的過程,可以達到服務不停機遷移數(shù)據(jù)源的目的,廢話不多說直接上代碼。
# 主數(shù)據(jù)源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test_test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
@Configuration
@ConditionalOnClass(value = {DataSource.class})
public class DataSourceDynamicConfig {
String DEFAULT = "default";
@ConfigurationProperties(prefix = "spring.datasource")
@Bean("defaultDataSource")
public DataSource setDefault(){
return DataSourceBuilder.create()
.type(DruidDataSource.class).build();
}
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier("defaultDataSource") DataSource defaultDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DEFAULT, defaultDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(defaultDataSource);
DataSourceContextHolder.setDataSourceName(DEFAULT);
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
在上述代碼中,我們使用@ConfigurationProperties注解將不同數(shù)據(jù)源的配置注入到DataSource實例中。然后,我們創(chuàng)建一個DynamicDataSource實例。我們使用targetDataSources屬性來存儲所有數(shù)據(jù)源,每個數(shù)據(jù)源都有一個標識符。defaultTargetDataSource屬性指定默認的數(shù)據(jù)源。
public class DynamicDataSource extends AbstractRoutingDataSource {
// 存儲所有數(shù)據(jù)源
private static Map<Object, Object> dataSources = new HashMap<>();
public void setDataSources(Map<Object, Object> sources){
this.setTargetDataSources(sources);
dataSources = sources;
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceName();
}
// 動態(tài)添加數(shù)據(jù)源
public static void setDataSource(String key,DataSource dataSource){
dataSources.putIfAbsent(key,dataSource);
}
protected DataSource determineTargetDataSource() {
String dataSourceName = determineCurrentLookupKey().toString();
DataSource dataSource = (DataSource) dataSources.get(dataSourceName);
if (dataSource == null) {
throw new IllegalStateException("DataSource '" + dataSourceName + "' is not configured");
}
return dataSource;
}
}
//存儲當前線程數(shù)據(jù)源
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceName(String name){
CONTEXT_HOLDER.set(name);
}
public static String getDataSourceName(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceName(){
CONTEXT_HOLDER.remove();
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataSourceEntity {
String url;
String userName;
String pwd;
String driverClassName;
}
準備數(shù)據(jù)測試數(shù)據(jù)表語句。
create database test_test;
CREATE TABLE `t_t` (
`id` int DEFAULT NULL,
`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='test'
插入兩個數(shù)據(jù)庫測試數(shù)據(jù)。
## test_test
INSERT INTO test_test.t_t
(id, name)
VALUES(1, 'test_test');
## full_dev
INSERT INTO test_test.t_t
(id, name)
VALUES(1, 'full_dev');
創(chuàng)建一個查詢TestMapper類。
@Mapper
public interface TestMapper {
@Select("select * from t_t")
Map<String,Object> selectMap();
}
創(chuàng)建SpringBoot Main函數(shù)。
@SpringBootApplication
@MapperScan(basePackages = {"com.xlc.common.datasource.mapper.test"})
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class,args);
}
}
@SpringBootTest(classes = Main.class)
@RunWith(SpringRunner.class)
public class TestApp {
String userName = "root";
String pwd = "root";
String url = "jdbc:mysql://192.168.15.130:32691/test_test?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false";
String driver = "com.mysql.cj.jdbc.Driver";
@Resource
TestMapper testMapper;
@Before
public void before(){
DataSource dataSource = DataSourceBuilder.create()
.url(url)
.username(userName)
.password(pwd)
.driverClassName(driver)
.build();
DynamicDataSource.setDataSource("hhh",dataSource);
}
@Test
public void t2(){
System.out.println(JSONUtil.toJsonStr(testMapper.selectMap()));
DataSourceContextHolder.setDataSourceName("hhh");
System.out.println(JSONUtil.toJsonStr(testMapper.selectMap()));
}
}
運行結果: