Usando uma tabela de banco de dados, processamento de dados de múltiplas fontes de dados

projeto Spring inicialização através da interface dinamicamente adicionar / remover uma fonte de dados, e em seguida, adicione uma fonte de dados, dinamicamente mudar de fontes de dados, e depois usar a fonte de dados após a mudança mybaties inquérito.

1. Em primeiro lugar, a fonte de dados necessário para criar uma tabela no banco de dados, as informações armazenadas na tabela de informações de conexão no banco de dados.

#数据源信息表
DROP TABLE IF EXISTS `datasource_config`;
CREATE TABLE IF NOT EXISTS `datasource_config`
(
    `id`       bigint(13)   NOT NULL AUTO_INCREMENT COMMENT '主键',
    `host`     varchar(255) NOT NULL COMMENT '数据库地址',
    `port`     int(6)       NOT NULL COMMENT '数据库端口',
    `username` varchar(100) NOT NULL COMMENT '数据库用户名',
    `password` varchar(100) NOT NULL COMMENT '数据库密码',
    `database` varchar(100) DEFAULT 0 COMMENT '数据库名称',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 COMMENT ='数据源配置表';

2. Código Principal

arquivo 2.1 pom

 pom necessidades de arquivo de informações seção para adicionar dependências e dependências de dados de conexão

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
      <groupId>tk.mybatis</groupId>
      <artifactId>mapper-spring-boot-starter</artifactId>
      <version>2.1.5</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>

classe 2.2 Configuração básica

DatasourceConfig, principalmente para construir a sua própria definição de uma fonte de dados DataSourceBuilder, que é colocado em um recipiente de Primavera

@Configuration
public class DatasourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.type(DynamicDataSource.class);
        return dataSourceBuilder.build();
    }
}

MybatisConfiguration principalmente para construir o passo anterior a configuração da fonte de dados MyBatis no `SqlSessionFactory`

@Configuration
@MapperScan(basePackages = "data.cloud.datas.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfiguration {
    /**
     * 创建会话工厂。
     *
     * @param dataSource 数据源
     * @return 会话工厂
     */
    @Bean(name = "sqlSessionFactory")
    @SneakyThrows
    public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
}

A principal lógica de fontes de dados dinâmicos 2.3

DatasourceConfigContextHolder ID de fonte de dados primário para segmentos que são usados ​​actualmente ligação para garantir que o mesmo fio através ThreadLocal não ser modificado

public class DatasourceConfigContextHolder {
    private static final ThreadLocal<Long> DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID);

    private static Long temp;
    /**
     * 设置默认数据源
     */
    public static void setDefaultDatasource() {
        DATASOURCE_HOLDER.remove();
        setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID);
    }

    /**
     * 获取当前数据源配置id
     */
    public static Long getCurrentDatasourceConfig() {
        //return DATASOURCE_HOLDER.get();
        return temp;
    }

    /**
     * 设置当前数据源配置id
     */
    public static void setCurrentDatasourceConfig(Long id) {
        DATASOURCE_HOLDER.set(id);
        temp = id;
    }
}

DynamicDataSource, herda da classe `com.zaxxer.hikari.HikariDataSource`, principalmente para mudar dinamicamente conexão de fonte de dados.

@Slf4j
public class DynamicDataSource extends HikariDataSource {
    @Override
    public Connection getConnection() throws SQLException {
        // 获取当前数据源 id
        Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig();
        // 根据当前id获取数据源
        HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id);
        if (null == datasource) {
            datasource = initDatasource(id);
        }
        return datasource.getConnection();
    }
    /**
     * 初始化数据源
     * @param id 数据源id
     * @return 数据源
     */
    private HikariDataSource initDatasource(Long id) {
        HikariDataSource dataSource = new HikariDataSource();

        // 判断是否是默认数据源
        if (DatasourceHolder.DEFAULT_ID.equals(id)) {
            // 默认数据源根据 application.yml 配置的生成
            DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class);
            dataSource.setJdbcUrl(properties.getUrl());
            dataSource.setUsername(properties.getUsername());
            dataSource.setPassword(properties.getPassword());
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        } else {
            // 不是默认数据源,通过缓存获取对应id的数据源的配置
            SourceConfig sourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id);
            if (sourceConfig == null) {
                throw new RuntimeException("无此数据源");
            }
            dataSource.setJdbcUrl(sourceConfig.buildJdbcUrl());
            dataSource.setUsername(sourceConfig.getUsername());
            dataSource.setPassword(sourceConfig.getPassword());
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        }
        // 将创建的数据源添加到数据源管理器中,绑定当前线程
        DatasourceHolder.INSTANCE.addDatasource(id, dataSource);
        return dataSource;
    }
}

DatasourceScheduler usado principalmente para a tarefa de agendamento

public enum DatasourceScheduler {
    /**
     * 当前实例
     */
    INSTANCE;
    private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
    private ScheduledExecutorService scheduler;
    DatasourceScheduler() {
        create();
    }
    private void create() {
        this.shutdown();
        this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement())));
    }
    private void shutdown() {
        if (null != this.scheduler) {
            this.scheduler.shutdown();
        }
    }
    public void schedule(Runnable task,long delay){
        this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
    }
}

DatasourceManager, usado principalmente para gerenciar fontes de dados, fonte de dados gravados última vez utilizado e determina se ou não usado por um longo tempo, ao longo de um determinado período de tempo não é usado, a conexão será lançado.

public class DatasourceManager {
    //默认释放时间
 
    private static final Long DEFAULT_RELEASE = 10L;
    //数据源
    @Getter
    private HikariDataSource dataSource;
    public HikariDataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(HikariDataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * 上一次使用时间
     */
    private LocalDateTime lastUseTime;
    public DatasourceManager(HikariDataSource dataSource) {
        this.dataSource = dataSource;
        this.lastUseTime = LocalDateTime.now();
    }
    /**
     * 是否已过期,如果过期则关闭数据源
     * @return 是否过期,{@code true} 过期,{@code false} 未过期
     */
    public boolean isExpired() {
        if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) {
            return false;
        }
        this.dataSource.close();
        return true;
    }
    /**
     * 刷新上次使用时间
     */
    public void refreshTime() {
        this.lastUseTime = LocalDateTime.now();
    }
}

DatasourceHolder, classe é usado principalmente para gerenciar a fonte de dados, através do `DatasourceScheduler` verificar regularmente se a fonte de dados não é usado por um longo tempo, ele libera o tempo limite de conexão.

public enum DatasourceHolder {
    //当前实例
    INSTANCE;
    //启动执行,定时5分钟清理一次
    DatasourceHolder() {
        DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000);
    }
    /**
     * 默认数据源的id
     */
    public static final Long DEFAULT_ID = -1L;
    /**
     * 管理动态数据源列表。
     */
    private static final Map<Long, DatasourceManager> DATASOURCE_CACHE = new ConcurrentHashMap<>();
    /**
     * 添加动态数据源
     * @param id         数据源id
     * @param dataSource 数据源
     */
    public synchronized void addDatasource(Long id, HikariDataSource dataSource) {
        DatasourceManager datasourceManager = new DatasourceManager(dataSource);
        DATASOURCE_CACHE.put(id, datasourceManager);
    }
    /**
     * 查询动态数据源
     * @param id 数据源id
     * @return 数据源
     */
    public synchronized HikariDataSource getDatasource(Long id) {
        if (DATASOURCE_CACHE.containsKey(id)) {
            DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id);
            datasourceManager.refreshTime();
            return datasourceManager.getDataSource();
        }
        return null;
    }
    /**
     * 清除超时的数据源
     */
    public synchronized void clearExpiredDatasource() {
        DATASOURCE_CACHE.forEach((k, v) -> {
            // 排除默认数据源
            if (!DEFAULT_ID.equals(k)) {
                if (v.isExpired()) {
                    DATASOURCE_CACHE.remove(k);
                }
            }
        });
    }
    /**
     * 清除动态数据源
     * @param id 数据源id
     */
    public synchronized void removeDatasource(Long id) {
        if (DATASOURCE_CACHE.containsKey(id)) {
            // 关闭数据源
            DATASOURCE_CACHE.get(id).getDataSource().close();
            // 移除缓存
            DATASOURCE_CACHE.remove(id);
        }
    }
}
DatasourceConfigCache,该类主要用于缓存数据源的配置,用户生成数据源时,获取数据源连接参数。
public enum DatasourceConfigCache {
    /**
     * 当前实例
     */
    INSTANCE;
    /**
     * 管理动态数据源列表。
     */
    private static final Map<Long, SourceConfig> CONFIG_CACHE = new ConcurrentHashMap<>();
    /**
     * 添加数据源配置
     */
    public synchronized void addConfig(Long id, SourceConfig config) {
        CONFIG_CACHE.put(id, config);
    }
    /**
     * 查询数据源配置
     */
    public synchronized SourceConfig getConfig(Long id) {
        if (CONFIG_CACHE.containsKey(id)) {
            return CONFIG_CACHE.get(id);
        }
        return null;
    }
    // 清除数据源配置
    public synchronized void removeConfig(Long id) {
        CONFIG_CACHE.remove(id);
        // 同步清除 DatasourceHolder 对应的数据源
        DatasourceHolder.INSTANCE.removeDatasource(id);
    }
}

configuração de fonte de dados 2.4 padrão e fonte de dados de comutação

DefaultDatasource,默认数据源
//默认数据源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultDatasource {
}
DatasourceSelectorAspect,主要用于切换数据源
@Aspect
@Component
public class DatasourceSelectorAspect {
    @Pointcut("execution(public * data.cloud.datas.controller.*.*(..))")
    public void datasourcePointcut(){
    }
    /**
     * 前置操作,拦截具体请求,获取header里的数据源id,设置线程变量里,用于后续切换数据源
     */
    @Before("datasourcePointcut()")
    public void doBefore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        // 排除不可切换数据源的方法
        DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class);
        if (null != annotation) {
            DatasourceConfigContextHolder.setDefaultDatasource();
        } else {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            String configIdInHeader = request.getHeader("Datasource-Config-Id");
            if (StringUtils.hasText(configIdInHeader)) {
                long configId = Long.parseLong(configIdInHeader);
                DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId);
            } else {
                DatasourceConfigContextHolder.setDefaultDatasource();
            }
        }
    }
    /**
     * 后置操作,设置回默认的数据源id
     */
    @AfterReturning("datasourcePointcut()")
    public void doAfter() {
        DatasourceConfigContextHolder.setDefaultDatasource();
    }
}

2.5 Ferramentas

SpringUtils

@Slf4j
@Service
@Lazy(false)
public class SpringUtil implements ApplicationContextAware, DisposableBean {
    private static ApplicationContext applicationContext = null;
    private static Logger log = LoggerFactory.getLogger(SpringUtil.class);
    //取得存储在静态变量中的ApplicationContext.
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    //实现ApplicationContextAware接口, 注入Context到静态变量中.
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringUtil.applicationContext = applicationContext;
    }
    //从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }
    //从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
    //清除SpringContextHolder中的ApplicationContext为Null.
    public static void clearHolder() {
        if (log.isDebugEnabled()) {
            log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
        }
        applicationContext = null;
    }
    /**
     * 发布事件
     * @param event 事件
     */
    public static void publishEvent(ApplicationEvent event) {
        if (applicationContext == null) {
            return;
        }
        applicationContext.publishEvent(event);
    }
    /**
     * 实现DisposableBean接口, 在Context关闭时清理静态变量.
     */
    @Override
    public void destroy() {
        SpringUtil.clearHolder();
    }
}

2.6 classe de inicialização

Após o arranque, utilize a lista de configuração de fonte de dados de consulta de fonte de dados padrão, seu cache para `DatasourceConfigCache` dentro, para uso posterior.

@SpringBootApplication
@EnableSwagger2
@EnableTransactionManagement
public class DatasApplication implements CommandLineRunner {
    @Resource
    private ConfigMapper configMapper;
    public static void main(String[] args) {
        SpringApplication.run(DatasApplication.class, args);
    }
    @Override
    public void run(String... args) {
        // 设置默认的数据源
        DatasourceConfigContextHolder.setDefaultDatasource();
        // 查询所有数据库配置列表
        List<SourceConfig> sourceConfigs = configMapper.selectAll();
        System.out.println("加载其余数据源配置列表: " + sourceConfigs);
        // 将数据库配置加入缓存
        sourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config));
    }
}

3. Teste

Adicionar valor Datasource-Config-Id de cabeçalho no cabeçalho da solicitação, o valor é o valor do ID de dados na tabela de origem

Publicado 36 artigos originais · ganhou elogios 19 · vê 30000 +

Acho que você gosta

Origin blog.csdn.net/qq_27182767/article/details/104053510
Recomendado
Clasificación