多数据源是写死成多个数据源 在启动时加载所有的数据源
多数据源实现代码地址 https://gitee.com/tothis/spring-boot-record/tree/master/data/multi-data-source
动态修改数据源 为有个主库 其余的数据源从主库中读取 且项目运行中也可动态增加删除数据源
动态数据源实现代码地址 https://gitee.com/tothis/spring-boot-record/tree/master/data/dynamic-data-source
本文描述动态数据源实现
# 数据库配置
spring:
datasource:
jdbc-url: jdbc:mysql://192.168.92.134:3306/center?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
CREATE DATABASE center CHARSET utf8mb4;
CREATE TABLE center.`table` (
`id` varchar(50) NOT NULL,
`url` varchar(255) NOT NULL,
`user_name` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `center`.`table`(`id`, `url`, `user_name`, `password`) VALUES ('db1', 'jdbc:mysql://192.168.10.200:3306/one?useUnicode=true&characterEncoding=utf8&useSSL=false', 'root', '123456');
INSERT INTO `center`.`table`(`id`, `url`, `user_name`, `password`) VALUES ('db2', 'jdbc:mysql://192.168.10.200:3306/two?useUnicode=true&characterEncoding=utf8&useSSL=false', 'root', '123456');
CREATE TABLE center.`user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(20) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
INSERT INTO center.`user` VALUES (1, 'center');
CREATE DATABASE one CHARSET utf8mb4;
CREATE TABLE one.`user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(20) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
INSERT INTO one.`user` VALUES (1, '李磊');
CREATE DATABASE two CHARSET utf8mb4;
CREATE TABLE two.`user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(20) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
INSERT INTO two.`user` VALUES (1, 'frank');
@MapperScan(basePackages = "com.example.mapper")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DynamicDateSourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDateSourceApplication.class, args);
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final String base = "center";
@Override
protected Object determineCurrentLookupKey() {
return DataSourceSwitch.get();
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description 线程安全的DataSourceEnum容器 并提供设置和获取DataSourceEnum函数
*/
public class DataSourceSwitch {
private static final ThreadLocal<String> DATA_SOURCE = ThreadLocal.withInitial(() -> DynamicDataSource.base);
private static List<String> dataSourceIds;
public static void set(String type) {
DATA_SOURCE.set(type);
}
public static String get() {
return DATA_SOURCE.get();
}
public static void reset() {
// 重设默认值 或清除数据都可以(因为已设置默认数据源)
// DATA_SOURCE.set(DynamicDataSource.base);
DATA_SOURCE.remove();
}
public static boolean isExist(String dataSourceId) {
return DataSourceSwitch.dataSourceIds.contains(dataSourceId);
}
public static void setDataSource(List<String> dataSourceIds) {
DataSourceSwitch.dataSourceIds = dataSourceIds;
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DynamicDataSource dynamicDataSource(DataSource dataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 此处设为空值 在springboot启动后重新配置
dynamicDataSource.setTargetDataSources(new HashMap());
dynamicDataSource.setDefaultTargetDataSource(dataSource);
return dynamicDataSource;
}
/**
* 事务 需使用DynamicDataSource配置事务
*
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description
*/
@Configuration
public class MyBatisConfig {
/**
* 根据数据源创建SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
return factoryBean.getObject();
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description
*/
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
@Target(ElementType.METHOD) // 可用在方法上
public @interface DataSourceType {
String value() default DynamicDataSource.base;
}
/**
* @author 李磊
* @datetime 2020/03/18 22:14
* @description
*/
@Component
@Aspect
public class DataSourceAspect {
@Before("@annotation(type)")
public void changeDataSource(JoinPoint point, DataSourceType type) {
String dataSourceId = type.value();
if (DataSourceSwitch.isExist(dataSourceId)) {
DataSourceSwitch.set(dataSourceId);
System.out.println("\033[36m数据源已切换[" + dataSourceId + "] - " + point.getSignature() + "\033[m");
} else {
System.out.println("\033[31m数据源不存在[" + dataSourceId + "] - " + point.getSignature() + "\033[m");
}
}
@After("@annotation(type)")
public void reset(JoinPoint point, DataSourceType type) {
DataSourceSwitch.reset();
System.out.println("\033[34m数据源已还原[" + DynamicDataSource.base + "] - " + point.getSignature() + "\033[m");
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:20
* @description
*/
@Slf4j
@Component
// springboot启动后会执行CommandLineRunner实现类的run方法
public class DataSourceRunner implements CommandLineRunner {
@Autowired
private DynamicDataSource dynamicDataSource;
@Autowired
private TableMapper tableMapper;
private static ExecutorService executor = Executors.newFixedThreadPool(4);
public boolean dataSourceTask() {
List<Table> tables = tableMapper.findAll();
DataSourceSwitch.setDataSource(tables.stream().map(Table::getId).collect(Collectors.toList()));
Map<Object, Object> dataSourceMap = tables.parallelStream().collect(Collectors.toMap(
Table::getId, dbManager -> {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setJdbcUrl(dbManager.getUrl());
hikariDataSource.setUsername(dbManager.getUserName());
hikariDataSource.setPassword(dbManager.getPassword());
hikariDataSource.setMaximumPoolSize(12);
hikariDataSource.setConnectionTimeout(60000);
hikariDataSource.setMinimumIdle(10);
hikariDataSource.setIdleTimeout(500000);
hikariDataSource.setMaxLifetime(540000);
hikariDataSource.setConnectionTestQuery("SELECT 1");
return hikariDataSource;
}
));
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.afterPropertiesSet();
return true;
}
@Override
public void run(String... args) throws Exception {
executor.submit(this::dataSourceTask).get();
}
}
/**
* @author 李磊
* @datetime 2020/03/18 22:16
* @description
*/
@RestController
public class UserController {
@Autowired
private DataSourceRunner dataSourceRunner;
@Autowired
private UserService userService;
@DataSourceType("db1")
@GetMapping("db1/{id}")
public User db1(@PathVariable Long id) {
return userService.findById(id);
}
@DataSourceType("db2")
@GetMapping("db2/{id}")
public User db2(@PathVariable Long id) {
return userService.findById(id);
}
@DataSourceType
@GetMapping("reload/{id}")
public boolean reload(@PathVariable Long id) {
userService.findById(id);
return dataSourceRunner.dataSourceTask();
}
}
完整代码参考 https://gitee.com/tothis/spring-boot-record/tree/master/data/dynamic-data-source