信息发布→ 登录 注册 退出

基于mybatis plus实现数据源动态添加、删除、切换,自定义数据源的示例代码

发布时间:2026-01-11

点击量:
目录
  • 简介
  • 代码示例
    • mavne依赖
    • 数据源增加、移除
    • 数据源切换
      • 基于AOP切换
      • 基于重写处理器
    • 自定义数据源

    简介

    基于springboot,mybatis plus集成了一套多数据源的解决方案,在使用时引入相应的插件dynamic-datasource-spring-boot-starter,可以实现数据源的动态添加、删除等功能,对于多租户或者分库等操作可以根据AOP切面代理到不同的数据源、实现单一系统数据隔离的目的。

    代码示例

    mavne依赖

    <!--mybatis-plus-->
    <dependency>
    	<groupId>com.baomidou</groupId>
    	<artifactId>mybatis-plus-boot-starter</artifactId>
    	<version>3.4.3.4</version>
    </dependency>
     
    <!--dynamic-datasource-->
    <dependency>
    	<groupId>com.baomidou</groupId>
    	<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    	<version>3.4.1</version>
    </dependency>

    数据源增加、移除

    @RestController
    @RequestMapping("/datasources")
    public class DataSourceController {
     
        @Resource
        private DataSource dataSource;
        @Resource
        private DefaultDataSourceCreator dataSourceCreator;
        
     
        @GetMapping("list")
        public Set<String> list() {
            DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
            return ds.getDataSources().keySet();
        }
     
        @PostMapping("add")
        public Set<String> add(@Validated @RequestBody DataSourceDTO dto) {
            DataSourceProperty dataSourceProperty = new DataSourceProperty();
            BeanUtils.copyProperties(dto, dataSourceProperty);
            DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
            DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
            ds.addDataSource(dto.getPollName(), dataSource);
            return ds.getDataSources().keySet();
        }
     
        @DeleteMapping("remove")
        public void remove(String name) {
            DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
            ds.removeDataSource(name);
        }
    }

    默认的数据源连接池加载顺序为: druid>hikaricp>beecp>dbcp>spring basic

    数据源切换

    基于AOP切换

    添加注解,排除不做切换的接口

    package com.starsray.dynamic.datasource.annotation;
     
    import java.lang.annotation.*;
     
    /**
     * <p>
     * 用户标识仅可以使用默认数据源
     * </p>
     *
     * @author starsray
     * @since 2025-11-10
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DefaultDs {
    }

    切面具体实现

    package com.starsray.dynamic.datasource.interceptor;
     
    import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
    import com.starsray.dynamic.datasource.annotation.DefaultDs;
    import com.starsray.dynamic.datasource.exception.ExceptionEnum;
    import com.starsray.dynamic.datasource.exception.GlobalException;
    import lombok.RequiredArgsConstructor;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.servlet.HandlerInterceptor;
     
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
     
    /**
     * <p>
     * 数据源选择器切面
     * </p>
     *
     * @author starsray
     * @since 2025-11-10
     */
    @Aspect
    @Component
    @RequiredArgsConstructor(onConstructor_ = @Autowired)
    public class DsInterceptor implements HandlerInterceptor {
     
        @Pointcut("execution(public * com.starsray.dynamic.datasource.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();
     
            // 排除不可切换数据源的方法
            DefaultDs annotation = method.getAnnotation(DefaultDs.class);
            if (null != annotation) {
                DynamicDataSourceContextHolder.push("master");
            } else {
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
                assert attributes != null;
                HttpServletRequest request = attributes.getRequest();
                String header = request.getHeader("tenantName");
                if (StringUtils.isNotBlank(header)) {
                    DynamicDataSourceContextHolder.push(header);
                } else {
                    throw new GlobalException(ExceptionEnum.NOT_TENANT);
                }
            }
        }
     
        /**
         * 后置操作,设置回默认的数据源id
         */
        @AfterReturning("datasourcePointcut()")
        public void doAfter() {
            DynamicDataSourceContextHolder.push("master");
        }
     
    }

    基于重写处理器

    mybatis plus提供了默认处理器来决定使用的数据源,可以重写处理器实现自定义参数,比如从请求header里面获取参数切换数据源。

    @DS("#header.tenantId")

    自定义处理器

    public class HeaderProcessor extends DsProcessor {
     
        private static final String HEADER = "#header";
     
        @Override
        public boolean matches(String key) {
            return key.startsWith(HEADER);
        }
     
        @Override
        public String doDetermineDatasource(MethodInvocation invocation, String key) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            return request.getHeader(key.substring(8));
        }
    }

    注册自定义处理器

    @Configuration
    public class CustomerDynamicDataSourceConfig{
     
       @Bean
       public DsProcessor dsProcessor() {
            DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
            DsSessionProcessor sessionProcessor = new DsSessionProcessor();
            DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
            headerProcessor.setNextProcessor(sessionProcessor);
            sessionProcessor.setNextProcessor(spelExpressionProcessor);
            return headerProcessor;
       }
    }

    如果有场景需要手动切换数据源,可以使用组件提供的工具来实现。

    DynamicDataSourceContextHolder.push("master");

    自定义数据源

    mybatis plus提供了一个接口来加载数据源信息。

    public interface DynamicDataSourceProvider {
        Map<String, DataSource> loadDataSources();
    }

    这个接口有一个抽象实现类AbstractDataSourceProvider,通过模板方法定义了加载数据源来源的方式,mybatis plus通过YmlDynamicDataSourceProvider实现了读取yml文件配置来初始化数据源的方式。

    public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {
        private static final Logger log = LoggerFactory.getLogger(AbstractDataSourceProvider.class);
        @Autowired
        private DefaultDataSourceCreator defaultDataSourceCreator;
     
        public AbstractDataSourceProvider() {
        }
     
        protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) {
            Map<String, DataSource> dataSourceMap = new HashMap(dataSourcePropertiesMap.size() * 2);
            Iterator var3 = dataSourcePropertiesMap.entrySet().iterator();
     
            while(var3.hasNext()) {
                Entry<String, DataSourceProperty> item = (Entry)var3.next();
                String dsName = (String)item.getKey();
                DataSourceProperty dataSourceProperty = (DataSourceProperty)item.getValue();
                String poolName = dataSourceProperty.getPoolName();
                if (poolName == null || "".equals(poolName)) {
                    poolName = dsName;
                }
     
                dataSourceProperty.setPoolName(poolName);
                dataSourceMap.put(dsName, this.defaultDataSourceCreator.createDataSource(dataSourceProperty));
            }
            return dataSourceMap;
        }
    }

    如果有需要从数据库加载数据源信息,可以重写AbstractJdbcDataSourceProvider中的executeStmt方法来加载数据库配置信息。示例:

    package com.digital.cnzz.dynamic.ds.provider;
     
    import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
    import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
    import com.digital.cnzz.dynamic.ds.config.DefaultDsConfig;
    import com.digital.cnzz.dynamic.ds.constant.DsDriverEnum;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
     
    import javax.annotation.Resource;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.HashMap;
    import java.util.Map;
     
    @Primary
    @Configuration
    public class DsProvider {
     
        @Resource
        private DefaultDsConfig defaultDsConfig;
     
        @Bean
        public DynamicDataSourceProvider jdbcDynamicDataSourceProvider() {
            return new AbstractJdbcDataSourceProvider(defaultDsConfig.getDriverClassName(), defaultDsConfig.getUrl(), defaultDsConfig.getUsername(), defaultDsConfig.getPassword()) {
                @Override
                protected Map<String, DataSourceProperty> executeStmt(Statement statement) {
                    Map<String, DataSourceProperty> dataSourcePropertiesMap = null;
                    ResultSet rs = null;
                    try {
                        dataSourcePropertiesMap = new HashMap<>();
                        rs = statement.executeQuery("SELECT * FROM DYNAMIC_DATASOURCE_INSTANCE");
                        while (rs.next()) {
                            String name = rs.getString("name");
                            DataSourceProperty property = new DataSourceProperty();
                            property.setDriverClassName(rs.getString("driver"));
                            property.setUrl(rs.getString("url"));
                            property.setUsername(rs.getString("username"));
                            property.setPassword(rs.getString("password"));
                            dataSourcePropertiesMap.put(name, property);
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (rs != null) {
                                rs.close();
                            }
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                        try {
                            statement.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
                    return dataSourcePropertiesMap;
                }
            };
        }
    }

    通过读取源码可以发现,如果还有其他需要自定义加载数据源的方式,只需要继承AbstractDataSourceProvider抽象类,实现DynamicDataSourceProvider接口,重写loadDataSources方法就可以实现自定义数据源。

    完整代码示例:https://gitee.com/starsray/dynamic-datasource

    在线客服
    服务热线

    服务热线

    4008888355

    微信咨询
    二维码
    返回顶部
    ×二维码

    截屏,微信识别二维码

    打开微信

    微信号已复制,请打开微信添加咨询详情!