Spring Boot 动态数据源切换

背景

随着互联网应用的快速发展,多数据源的需求日益增多。Spring Boot 以其简洁的配置和强大的功能,成为实现动态数据源切换的理想选择。本文将通过具体的配置和代码示例,详细介绍如何在 Spring Boot 应用中实现动态数据源切换,帮助开发者高效应对不同业务场景下的数据管理需求。无论是读写分离还是数据隔离,都能轻松搞定。

AOP动态代理

AOP注解

复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String name();
}

AOP切面类

复制代码
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.aliyunai.db.TargetDataSource)")
    public void dataSourcePointcut() {
    }

    @Before("dataSourcePointcut()")
    public void changeDataSource(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        if (targetDataSource != null) {
            String dataSourceName = targetDataSource.name();
            DataSourceContextHolder.setDataSourceKey(dataSourceName);
        }
    }

    @After("dataSourcePointcut()")
    public void clearDataSource(JoinPoint point) {
        DataSourceContextHolder.clearDataSourceKey();
    }
}

数据源配置

复制代码
@Configuration
public class DataSourceConfig {


    @Bean(name = "master")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .driverClassName("")
                .url("")
                .username("")
                .password("")
                .build();
    }

    @Bean(name = "slave")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .driverClassName("")
                .url("")
                .username("")
                .password("")
                .build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("master") DataSource master,
                                               @Qualifier("slave") DataSource slave) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", master);
        targetDataSources.put("slave", slave);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(master);
        return dynamicDataSource;
    }

}

线程上下文

复制代码
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
}

动态数据源设置

复制代码
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceKey = DataSourceContextHolder.getDataSourceKey();
        logger.info("Determining current data source: {}", dataSourceKey);
        return dataSourceKey;
    }
}

service类

复制代码
@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    @TargetDataSource(name = "master")
    public User queryFromPrimary() {
        User user = userMapper.selectById(1);
        return user;
    }

    @TargetDataSource(name = "slave")
    public User queryFromSecondary() {
        User user = userMapper.selectById(1L);
        return user;
    }
}

Mybatis 拦截器

复制代码
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class}
        )})
public class DynamicDataSourceInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);

    // 假设这里有一个获取当前数据源标识的方法,你需要根据实际项目中的实现来替换
    private String getCurrentDataSourceKey() {
        // 示例:从某个上下文持有者中获取数据源标识,这里只是示意,实际要替换
        return DataSourceContextHolder.getDataSourceKey();
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        //对单个表进行处理
        logger.info("sql:"+sql);
       
        String mappedStatementId = mappedStatement.getId();
        //对所有查询进行处理
        if (mappedStatementId.contains("query")) {
            String table = getTable(sql);
            if (table.equals("user")){
                DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());
            }
        //增删改
        }else if (mappedStatementId.contains("update")) {
            DataSourceContextHolder.setDataSourceKey(getCurrentDataSourceKey());
        }

        return invocation.proceed();
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }


    private static String getTable(String sql) {
        // 定义正则表达式模式,用于匹配 "from" 和 "where" 之间的表名
        Pattern pattern = Pattern.compile("FROM\\s+(\\w+)\\s+WHERE");
        Matcher matcher = pattern.matcher(sql);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }
}
相关推荐
小吴编程之路1 小时前
MySQL 索引核心特性深度解析:从底层原理到实操应用
数据库·mysql
~莫子1 小时前
MySQL集群技术
数据库·mysql
HalvmånEver1 小时前
7.高并发内存池大页内存申请释放以及使用定长内存池脱离new
java·spring boot·spring
凤山老林1 小时前
SpringBoot 使用 H2 文本数据库构建轻量级应用
java·数据库·spring boot·后端
就不掉头发1 小时前
Linux与数据库进阶
数据库
与衫1 小时前
Gudu SQL Omni 技术深度解析
数据库·sql
咖啡の猫2 小时前
Redis桌面客户端
数据库·redis·缓存
oradh2 小时前
Oracle 11g数据库软件和数据库静默安装
数据库·oracle
赶路人儿2 小时前
UTC时间和时间戳介绍
java·开发语言
dreamread2 小时前
【SpringBoot整合系列】SpringBoot3.x整合Swagger
java·spring boot·后端