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
- Autopreenchimento Automático: O Spring Boot detecta automaticamente as configurações relacionadas à fonte de dados dinâmica.
- Configuração AOP: Ao entrar no componente
DynamicDataSourceAutoConfiguration, o sistema registra dois interceptores de corte (advice) viaDynamicDataSourceAopConfiguration. - Carregamento das Fontes: A classe
DynamicRoutingDataSource, que implementaInitializingBean, lê as configurações do arquivoapplication.ymle armazena cada fonte de dados no mapa internodataSourceMapusando o métodoaddDataSource(). - Interceptação por Anotação: O interceptor
DynamicDataSourceAnnotationInterceptorcaptura chamadas em métodos marcados com@DS, extrai o nome da fonte especificada e o salva em um contexto thread-local. - Contexto Thread-Safe: O
DynamicDataSourceContextHolderutilizaThreadLocalpara manter uma pilha (stack) de nomes de fontes ativas, permitindo operações aninhadas. - 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).
- Seleção da Fonte: No método
getDataSource()doDynamicRoutingDataSource, se o nome atual for nulo, usa-se a fonte primária; caso contrário, busca-se nadataSourceMapcom base no nome armazenado. - 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.