SpringBoot项目实现动态切换数据源,支持hibernate、mybatis、activity

本文介绍了一种在Spring框架下实现动态数据源切换的方法,通过自定义数据源处理器和切面,实现在不同业务场景下自动切换数据库连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、创建文件 ApplicationContextHandler.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextHandler implements ApplicationContextAware {

    private static ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHandler.context = applicationContext;
    }
    
    public static Object getBean(String name) {
        return ApplicationContextHandler.context.getBean(name);
    }
    
    public static Object getContext() {
        return ApplicationContextHandler.context;
    }

}
 

2、创建 DynamicDataSource.java

import java.util.Map;

import javax.sql.DataSource;

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

public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
     * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }

    /**
     * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 设置默认数据源
     * 
     * @param defaultDataSource
     */
    public void setDefaultDataSource(Object defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
    }

    /**
     * 设置数据源
     * 
     * @param dataSources
     */
    public void setDataSources(Map<Object, Object> dataSources) {
        super.setTargetDataSources(dataSources);
        // 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
    }
}
3、创建DynamicDataSourceContextHolder.java

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
        /**
         * 将 master 数据源的 key作为默认数据源的 key
         */
        @Override
        protected String initialValue() {
            return "dataSource";
        }
    };
    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();
    /**
     * 切换数据源
     * @param key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }
    /**
     * 获取数据源
     * @return
     */
    public static String getDataSourceKey() {
        return contextHolder.get();
    }
    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
    /**
     * 判断是否包含数据源
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }
    /**
     * 添加数据源keys
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}
 

4、创建 DynamicDataSourceInit.java

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import com.alibaba.druid.pool.DruidDataSource;
import com.lease.api.common.dao.CompanyDBDao;
import com.lease.api.common.model.CompanyDB;
import com.lease.api.common.util.JasyptUtil;

@Configuration
public class DynamicDataSourceInit {
    /**
     * LOG输出
     */
    private static final Logger log = Logger.getLogger(DynamicDataSourceInit.class);

    @Value("${jasypt.encryptor.password}")
    private String password;

    @PostConstruct
    public void InitDataSource() {
        log.info("=====初始化动态数据源=====");
        DynamicDataSource dynamicDataSource =
            (DynamicDataSource) ApplicationContextHandler.getBean("dynamicDataSource");
        DruidDataSource dataSource =
            (DruidDataSource) ApplicationContextHandler.getBean("dataSource");
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("dataSource", dataSource);

        CompanyDBDao companyDBDao =
            (CompanyDBDao) ApplicationContextHandler.getBean("companyDBDao");
        List<CompanyDB> CompanyDBs = companyDBDao.findByDelete();
        if (CompanyDBs != null && CompanyDBs.size() > 0) {
            for (CompanyDB companyDB : CompanyDBs) {
                log.info(companyDB.toString());
                DruidDataSource ds = new DruidDataSource();
                ds.setUrl(companyDB.getDatasourceUrl());
                ds.setUsername(companyDB.getDatasourceUsername());// 用户名
                ds.setPassword(JasyptUtil.decyptPwd(password, companyDB.getDatasourcePassword()));// 密码
                ds.setInitialSize(1);
                ds.setMaxActive(20);
                ds.setMinIdle(0);
                ds.setMaxWait(30000);
                ds.setValidationQuery("select user()");
                ds.setTestOnBorrow(false);
                ds.setTestWhileIdle(true);
                ds.setPoolPreparedStatements(false);
                dataSourceMap.put(companyDB.getId(), ds);
            }
        }
        // 设置数据源
        dynamicDataSource.setDataSources(dataSourceMap);
        /**
         * 必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的
         * resolvedDataSources,也只有这样,动态切换才会起效
         */
        dynamicDataSource.afterPropertiesSet();
    }

}
 

5、创建切面文件DynamicDataSourceAspect.java

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.lease.api.common.bean.LoginInfo;
import com.lease.api.common.util.JSONUtil;

@Aspect
@Component
@Order(1) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {

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

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("execution(* com.lease.api..*.*(..)) ")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        String token = getToken();
        if (StringUtils.isBlank(token)) {
            logger.info("未登录");
        } else {
            LoginInfo loginInfo = getLoginInfo(token);
            if (loginInfo == null) {
                logger.info("未登录");
            } else {
                String companyId = loginInfo.getStaff().getCompanyId();
                logger.info("当前租户Id:" + companyId);
                if (StringUtils.isNotBlank(companyId)) {
                    DynamicDataSourceContextHolder.setDataSourceKey(companyId);
                }
            }
        }
        Object result = jp.proceed();
        DynamicDataSourceContextHolder.clearDataSourceKey();
        return result;
    }

    /**
     * 获取token
     * 
     * @return
     */
    public String getToken() {
        ServletRequestAttributes servletRequestAttributes =
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (servletRequestAttributes == null) return null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        if (request == null) return null;
        String token = request.getHeader("Auth-Token");
        logger.info("getLoginInfo() start. Auth-Token=" + token);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter("token");
        }
        return token;
    }

    /**
     * 根据token获取登录人
     * 
     * @param token
     * @return
     */
    public LoginInfo getLoginInfo(String token) {
        logger.info("getLoginInfo(token) start. token=" + token);
        String json = redisTemplate.opsForValue().get(token);
        if (StringUtils.isBlank(json)) {
            return null;
        } else {
            return JSONUtil.toBean(json, LoginInfo.class);
        }
    }
}
 

6、配置动态数据源Bean

    @Bean
    @Primary
    @Qualifier("dataSource")
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));// 用户名
        dataSource.setPassword(env.getProperty("spring.datasource.password"));// 密码
        dataSource.setInitialSize(1);
        dataSource.setMaxActive(20);
        dataSource.setMinIdle(0);
        dataSource.setMaxWait(30000);
        dataSource.setValidationQuery("select user()");
        dataSource.setTestOnBorrow(false);
        dataSource.setTestWhileIdle(true);
        dataSource.setPoolPreparedStatements(false);
        return dataSource;
        // return DataSourceBuilder.create().build();
    }

    @Bean
    @Qualifier("dynamicDataSource")

    //@ConfigurationProperties(prefix = "spring.datasource.activiti")//项目中含有activity流程图需要添加此注解
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("dataSource", dataSource());
        // 将 master 数据源作为默认指定的数据源
        dynamicDataSource.setDefaultDataSource(dataSource());
        // 将 master 和 slave 数据源作为指定的数据源
        dynamicDataSource.setDataSources(dataSourceMap);
        return dynamicDataSource;
    }

7、创建文件配置Bean(hibernate)

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManageFactoryPrimary",
    transactionManagerRef = "transactionManagerPrimary",
    basePackages = {"com.lease.api.common.dao"})
public class EntityManageConfig {

    @Autowired
    @Qualifier("dynamicDataSource")
    private DataSource dynamicDataSource;

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManageFactory(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManageFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean
        entityManageFactory(EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean entityManagerFactory =
            builder.dataSource(dynamicDataSource).packages("com.lease.api.common.model")// PathEnum自定义的路径枚举类
                .persistenceUnit("primaryPersistenceUnit") // 使用持久化单元配置
                .build();
        return entityManagerFactory;
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManageFactory(builder).getObject());
    }

}
 

8、创建文件配置Bean(mybatis)

import javax.sql.DataSource;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@Configuration
@MapperScan({"com.lease.api.process.dao"}) // "org.activiti.engine.impl.persistence.entity"
public class MybatisPlusConfig {
    @Autowired
    @Qualifier("dynamicDataSource")
    private DataSource dynamicDataSource;

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
        sessionFactory.setDataSource(dynamicDataSource);
        // // 扫描Model
        sessionFactory.setTypeAliasesPackage(// ,org.activiti.engine.impl.persistence.entity
            "com.lease.api.process.entity");
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // 扫描映射文件 ,classpath*:org/activiti/db/mapping/entity/*.xml
        sessionFactory.setMapperLocations(resolver.getResources("classpath*:mybatis/*Mapper.xml"));
        return sessionFactory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}
 

总结:

数据库动态查询所有数据源启动加载;

实体类CompanyDB包含url,username,password等关键信息

实时切换代码 DynamicDataSourceContextHolder.setDataSourceKey(companyId);

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值