Implementação de Troca Dinâmica de Fontes de Dados com o @DS

Introdução

Em aplicações modernas, o uso de múltiplas fontes de dados é uma necesidade frequente, especialmente em cenários de leitura e escrita separadas. A configuração manual de DataSource e SqlSessionTemplate torna o código complexo e difícil de manter. Embora o projeto dynamic-datasource possa não ser amplamente conhecido, seu time de desenvolvimento é responsável por outro projeto popular: MyBatis-Plus. Neste artigo, exploramos como a anotação @DS permite trocar dinamicamente entre fontes de dados.

Utilização da Anotação @DS

A anotação @DS pode ser aplicada em classes ou métodos. Exemplo de uso:

@Service
@DS("slave")
public class UserServiceImpl implements UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<Map<String, Object>> getAllUsers() {
        return jdbcTemplate.queryForList("SELECT * FROM user");
    }

    @Override
    @DS("slave_1")
    public List<Map<String, Object>> findByAge(int minAge) {
        return jdbcTemplate.queryForList("SELECT * FROM user WHERE age > ?", minAge);
    }
}

Princípio de Funcionamento da Troca Dinâmica

  1. Autopreenchimento Automático: O Spring Boot detecta automaticamente as configurações relacionadas à fonte de dados dinâmica.
  2. Configuração AOP: Ao entrar no componente DynamicDataSourceAutoConfiguration, o sistema registra dois interceptores de corte (advice) via DynamicDataSourceAopConfiguration.
  3. Carregamento das Fontes: A classe DynamicRoutingDataSource, que implementa InitializingBean, lê as configurações do arquivo application.yml e armazena cada fonte de dados no mapa interno dataSourceMap usando o método addDataSource().
  4. Interceptação por Anotação: O interceptor DynamicDataSourceAnnotationInterceptor captura chamadas em métodos marcados com @DS, extrai o nome da fonte especificada e o salva em um contexto thread-local.
  5. Contexto Thread-Safe: O DynamicDataSourceContextHolder utiliza ThreadLocal para manter uma pilha (stack) de nomes de fontes ativas, permitindo operações aninhadas.
  6. Pilha para Aninhamento: A utilização de uma pilha é essencial quando há chamadas aninhdaas (ex: método A chama método B), pois garante que a fonte correta seja restaurada após o retorno, seguindo a regra LIFO (último a entrar, primeiro a sair).
  7. Seleção da Fonte: No método getDataSource() do DynamicRoutingDataSource, se o nome atual for nulo, usa-se a fonte primária; caso contrário, busca-se na dataSourceMap com base no nome armazenado.
  8. Execução e Limpeza: Após invocar o método real com invocation.proceed(), a fonte é removida da pilha, evitando interferência em chamadas subsequentes.

Configuração de Aplicação

Para evitar conflitos com bibliotecas como Druid, é necessário excluir sua configuração automática. Veja o exemplo de configuração correta:

server:
  port: 8081

spring:
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://localhost:3306/message?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false
          username: root
          password: zhaobo
          driver-class-name: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource
        db2:
          url: jdbc:mysql://192.168.25.131:3306/userdb?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false
          username: root
          password: zhaobo
          driver-class-name: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource
      druid:
        initial-size: 5
        max-active: 10
        min-idle: 5
        max-wait: 60000

  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure

Observação: Se a exclusão não for feita, ocorrerá erro ao iniciar a aplicação: Failed to determine a suitable driver class.

Tags: dynamic-datasource Spring Boot @DS annotation DataSource routing ThreadLocal

Publicado em 9-25 11:16