spring-boot动态修改数据源

多数据源是写死成多个数据源 在启动时加载所有的数据源
多数据源实现代码地址 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

本文描述动态数据源实现

  1. 配置文件 application.yml
# 数据库配置
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
  1. 数据库脚本
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');
  1. 启动类排除数据源自动注入
@MapperScan(basePackages = "com.example.mapper")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DynamicDateSourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(DynamicDateSourceApplication.class, args);
    }
}
  1. 自定义数据源
/**
 * @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();
    }
}
  1. 线程安全的DataSourceEnum容器
/**
 * @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;
    }
}
  1. 数据源配置
/**
 * @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);
    }
}
  1. mybatis配置
/**
 * @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();
    }
}
  1. 自定义注解和aop
/**
 * @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");
    }
}
  1. springboot启动加载数据源
/**
 * @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();
    }
}
  1. 测试controller
/**
 * @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

你可能感兴趣的:(java)