Implementação de Troca de Múltiplas Fontes de Dados com Anotações Personalizadas no Spring Boot

  1. Dependências Principias
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/>
</parent>
    
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

  1. Configuração do application.yml
# Configuração de fontes de dados
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        druid:
            # Fonte de dados primária
            principal:
                url: jdbc:mysql://127.0.0.1:3306/principal?characterEncoding=UTF-8
                username: root
                password: root
            # Fonte de dados secundária
            secundaria:
                enabled : true
                url: jdbc:mysql://127.0.0.1:3306/secundaria?characterEncoding=UTF-8
                username: root
                password: root
            
            # Número inicial de conexões
            initial-size: 10
            # Número máximo de conexões no pool
            max-active: 100
            # Número mínimo de conexões no pool
            min-idle: 10
            # Tempo máximo de espera por uma conexão em milissegundos
            max-wait: 60000
            # Habilitar PSCache e definir o tamanho para cada conexão
            pool-prepared-statements: true
            max-pool-prepared-statement-per-connection-size: 20
            # Intervalo para verificação de conexões ociosas em milissegundos
            timeBetweenEvictionRunsMillis: 60000
            # Tempo mínimo de vida de uma conexão no pool em milissegundos
            min-evictable-idle-time-millis: 300000
            validation-query: SELECT 1 FROM DUAL
            test-while-idle: true
            test-on-borrow: false
            test-on-return: false
            stat-view-servlet:
                enabled: true
                url-pattern: /druid/*
            filter:
                stat:
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: false
                wall:
                    config:
                        multi-statement-allow: true

  1. Implementação

3.1 - Enumeração OrigemDados e Enotação @OrigemDados

/**
 * Tipos de origem de dados disponíveis
 */
public enum OrigemDados
{
    /**
     * Fonte de dados primária
     */
    PRINCIPAL,

    /**
     * Fonte de dados secundária
     */
    SECUNDARIA
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Anotação para seleção dinâmica de origem de dados
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OrigemDados
{
    /**
     * Nome da origem de dados a ser utilizada
     */
    public OrigemDados value() default OrigemDados.PRINCIPAL;
}

3.2 - Gerenciador de Conetxto de Origem de Dados

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Gerenciador de contexto para alternância de origens de dados
 */
public class GerenciadorContextoDados
{
    public static final Logger logger = LoggerFactory.getLogger(GerenciadorContextoDados.class);

    /**
     * ThreadLocal mantém variáveis específicas para cada thread,
     * permitindo que cada thread altere sua própria cópia sem afetar outras threads.
     */
    private static final ThreadLocal<String> CONTEXTO = new ThreadLocal<>();

    /**
     * Define a origem de dados atual
     */
    public static void definirOrigemDados(String tipoOrigem)
    {
        logger.info("Alternando para origem de dados: {}", tipoOrigem);
        CONTEXTO.set(tipoOrigem);
    }

    /**
     * Obtém a origem de dados atual
     */
    public static String obterOrigemDados()
    {
        return CONTEXTO.get();
    }

    /**
     * Limpa a origem de dados do contexto
     */
    public static void limparOrigemDados()
    {
        CONTEXTO.remove();
    }
}

3.3 - Extensão de AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * Implementação de fonte de dados dinâmica
 */
public class FonteDadosDinamica extends AbstractRoutingDataSource
{
    public FonteDadosDinamica(DataSource fonteDadosPadrao, Map<Object, Object> fontesDadosAlvo)
    {
        super.setDefaultTargetDataSource(fonteDadosPadrao);
        super.setTargetDataSources(fontesDadosAlvo);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        return GerenciadorContextoDados.obterOrigemDados();
    }
}

3.4 - Definição do Aspecto

import com.starfast.admin.common.annotation.OrigemDados;
import com.starfast.admin.datasource.GerenciadorContextoDados;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Aspecto para manipulação de múltiplas origens de dados
 */
@Aspect
@Order(1)
@Component
public class AspectoOrigemDados
{
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.duchong.common.annotation.OrigemDados)")
    public void pontoCorteDados()
    {
        // Ponto de corte para métodos anotados
    }

    @Around("pontoCorteDados()")
    public Object interceptor(ProceedingJoinPoint ponto) throws Throwable
    {
        MethodSignature assinatura = (MethodSignature) ponto.getSignature();

        Method metodo = assinatura.getMethod();

        OrigemDados origemDados = metodo.getAnnotation(OrigemDados.class);

        if (origemDados != null)
        {
            GerenciadorContextoDados.definirOrigemDados(origemDados.value().name());
        }

        try
        {
            return ponto.proceed();
        }
        finally
        {
            // Remove a origem de dados após a execução do método
            GerenciadorContextoDados.limparOrigemDados();
        }
    }
}

3.5 - Configuração do Spring

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.starfast.admin.common.enums.OrigemDados;
import com.starfast.admin.datasource.FonteDadosDinamica;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * Configuração do Druid para múltiplas origens de dados
 */
@Configuration
public class ConfiguracaoDruid
{
    @Bean
    @ConfigurationProperties("spring.datasource.druid.principal")
    public DataSource fonteDadosPrincipal()
    {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.secundaria")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.secundaria", name = "enabled", havingValue = "true")
    public DataSource fonteDadosSecundaria()
    {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "fonteDadosDinamica")
    @Primary
    public FonteDadosDinamica fonteDados()
    {
        Map<Object, Object> mapasFontesDados = new HashMap<>();
        mapasFontesDados.put(OrigemDados.PRINCIPAL.name(), fonteDadosPrincipal());
        mapasFontesDados.put(OrigemDados.SECUNDARIA.name(), fonteDadosSecundaria());
        return new FonteDadosDinamica(fonteDadosPrincipal(), mapasFontesDados);
    }
}

3.6 - Utilização

Para alternar a origem de dados em um método específico, adicione a anotação:

@OrigemDados(value = OrigemDados.SECUNDARIA)

Tags: spring-boot multi-datasource AOP Annotation Druid

Publicado em 7-5 01:12