springBoot 动态数据源以及Mybatis多数据源

前言

在开发过程中可能需要用到多个数据源,比如一个项目(MySQL)就是和(SQL Server)混合使用,就需要使用多数据源;如果业务场景比较复炸,可以使用动态数据源,灵活切换,典型的应用就是读写分离。下面分两个模块来配置数据源,大家可以根据自己实际情况配置。

多数据源

禁用DataSourceAutoConfiguration

如果DataSourceAutoConfiguration不禁用的话,就会报错,多个数据源,无法装配哪一个。在springBoot的主程序入口的注解

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class
})

配置application.properties

#datasource 这是自动装配的默认配置禁止,这里不能使用
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/api_resources?#autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
#spring.datasource.username=api
#spring.datasource.password=api
#db1
spring.datasource.db1.url=jdbc:mysql://127.0.0.1:3306/api_resources?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.db1.username=api
spring.datasource.db1.password=api
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
#db2
spring.datasource.db2.url=jdbc:mysql://127.0.0.1:3306/ssm?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.db2.username=api
spring.datasource.db2.password=api
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver

配置dataSource

配置两个数据源,分别为db1,和db2

/**
 * Author: Starry.Teng
 * Email: tengxing7452@163.com
 * Date: 17-11-1
 * Time: 下午9:14
 * Describe: DataSource Config
 */
@Configuration
public class DataSourceConfig {
    @Autowired
    Environment env;

    @Bean(name = "ds1")
    public DataSource dataSource1() {
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setUrl(env.getProperty("spring.datasource.db1.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.db1.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.db1.password"));
        dataSource.setDriverClassName(env.getProperty("spring.datasource.db1.driver-class-name"));
        return dataSource;
    }

    @Bean(name = "ds2")
    public DataSource dataSource2() {
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setUrl(env.getProperty("spring.datasource.db2.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.db2.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.db2.password"));
        dataSource.setDriverClassName(env.getProperty("spring.datasource.db2.driver-class-name"));
        return dataSource;
    }

}

Mybatis配置

为两个数据源分贝配置一个MybatisDb1Config类和MybatisDb2Config类,对数据源进行管理,注意他们所管理的包是不同的。

/**
 * Author: Starry.Teng
 * Email: tengxing7452@163.com
 * Date: 17-11-1
 * Time: 下午9:15
 * Describe: MybatisDb1 Config
 */
@Configuration
@MapperScan(basePackages = {"cn.yjxxclub.demo.datasource.dao.db1"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDb1Config {

    @Qualifier("ds1")
    @Autowired
    DataSource dataSource;

    @Bean(name = "sqlSessionFactory1")
    public SqlSessionFactory sqlSessionFactory1() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mappers/db1/*.xml"));
        factoryBean.setTypeAliasesPackage("cn.yjxxclub.demo.datasource.model");
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
        return template;
    }
}
/**
 * Author: Starry.Teng
 * Email: tengxing7452@163.com
 * Date: 17-11-1
 * Time: 下午9:26
 * Describe: MybatisDb2 Config
 */
@Configuration
@MapperScan(basePackages = {"cn.yjxxclub.demo.datasource.dao.db2"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDb2Config {

    @Qualifier("ds2")
    @Autowired
    DataSource dataSource;

    @Bean(name = "sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mappers/db2/*.xml"));
        factoryBean.setTypeAliasesPackage("cn.yjxxclub.demo.datasource.model");
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
        return template;
    }
}

编写Dao和Mapper

Mapper.java和mapper.xml在basePackages和classpath对应放置即可,和springboot整合Mybtis一样,这里就不说了,然后正常启动即可。代码在此:https://github.com/tengxing/Multiple-dataSources

数据源动态切换

数据源注册

/**
 * Author: Starry.Teng
 * Email: tengxing7452@163.com
 * Date: 17-11-1
 * Time: 下午9:14
 * Describe: DataSource Config
 */
@Configuration
public class DataSourceConfig {
    @Autowired
    Environment env;

    @Bean(name = "ds1")
    public DataSource dataSource1() {
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setUrl(env.getProperty("spring.datasource.db1.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.db1.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.db1.password"));
        dataSource.setDriverClassName(env.getProperty("spring.datasource.db1.driver-class-name"));
        return dataSource;
    }

    @Bean(name = "ds2")
    public DataSource dataSource2() {
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setUrl(env.getProperty("spring.datasource.db2.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.db2.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.db2.password"));
        dataSource.setDriverClassName(env.getProperty("spring.datasource.db2.driver-class-name"));
        return dataSource;
    }

    @Bean(name = "dynamicDS1")//注意这个bean是mybatis的sqlSessionFacatory所管理的dataSource
    public DataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource1());

        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(5);
        dsMap.put("ds1", dataSource1());
        dsMap.put("ds2", dataSource2());

        dynamicDataSource.setTargetDataSources(dsMap);

        return dynamicDataSource;
    }

}

动态数据源编写

需要继承AbstractRoutingDataSource类

/**
 * Author: http://blog.csdn.net/neosmith/article/details/61202084
 * Date: 17-11-2
 * Time: 下午2:56
 * Describe: 动态数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为{}", DataSourceContextHolder.getDB());

        return DataSourceContextHolder.getDB();
    }
}

编写DataSourceContextHolder

/**
 * Author: http://blog.csdn.net/neosmith/article/details/61202084
 * Date: 17-11-2
 * Time: 下午2:56
 * Describe: DataSource ContextHolder
 */
public class DataSourceContextHolder {
    public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);

    /**
     * 默认数据源
     */
    public static final String DEFAULT_DS = "ds1";

    /**
     * 获取当前线程
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    public static void setDB(String dbType) {
        log.debug("切换到{}数据源", dbType);
        contextHolder.set(dbType);
    }

    // 获取数据源名
    public static String getDB() {
        return (contextHolder.get());
    }

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }
}

咋们到时候只要调用setDB()方法即可实现数据源的切换,下面使用Aop的方式进行动态切换。

编写代理类

/**
 * Author: http://blog.csdn.net/neosmith/article/details/61202084
 * Date: 17-11-2
 * Time: 下午3:01
 * Describe: 动态数据源代理类
 * 逻辑:对方法@DB注解的方法进行切面换数据源操作
 */
@Aspect
@Component
@Order(value=-1) //保证该AOP在@Transactional之前执行
public class DynamicDataSourceAspect {

    @Before("@annotation(DB)")
    public void beforeSwitchDS(JoinPoint point){

        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();

        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = DataSourceContextHolder.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);

            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(DB.class)) {
                DB annotation = method.getAnnotation(DB.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 切换数据源
        DataSourceContextHolder.setDB(dataSource);

    }
    @After("@annotation(DB)")
    public void afterSwitchDS(JoinPoint point){

        DataSourceContextHolder.clearDB();

    }
}

自定义注解

/**
 * Author: Starry.Teng
 * Email: tengxing7452@163.com
 * Date: 17-11-2
 * Time: 下午3:00
 * Describe:
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.METHOD
})
public @interface DB {
    String value() default "ds1";
}

serviceImpl

    @DB("ds1")
    public Object getDs1(){

        Singer singer = singerMapper.findByName("陈奕迅");
        logger.info("/n"+singer);
        return null;
    }

    @DB("ds2")
    public Object getDs2(){
        Book book = bookMapper.findById(bookMapper.list().get(0).getId());
        logger.info(book+"/n");
        return null;
    }

最后调用吧!!!项目在此:https://github.com/tengxing/DynamicDataSource

两者比较

类别多数据源动态数据源
作用配置多个数据源,分模块开发通过灵活的手段对数据库进行灵活切库
原理实例化多个SsqlSessionFactorySpring+AOP
作用框架MybatisSpring
应用场景解耦比较大的项目,模块化开发主从分布,读写分离

知识点

上面的两种方式都达到了”换数据源”目的,只是实现的原理和方式不一样而已,都是优虐,还是那句话,没有最好的方法,只有在特定的环境下最适合的解决方案。

参考文章

http://blog.csdn.net/neosmith/article/details/61202084
http://www.cnblogs.com/hdwang/p/7041096.html
http://blog.csdn.net/catoop/article/details/50575038
https://github.com/zeq9069/koala/tree/master/src/main/java/org/kyrin/koala

### Spring Boot 中基于 MyBatis动态多数据源配置 #### 禁用默认的数据源自动配置 为了防止 Spring Boot 自动加载 `spring.datasource.*` 属性并创建单数据源实例,在启动类的 `@SpringBootApplication` 注解中需要排除 `DataSourceAutoConfiguration` 类。这可以通过设置 `exclude` 属性完成[^1]。 ```java @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` --- #### 定义多个数据源 在项目中定义多个数据源时,可以分别创建不同的 Bean 实例来表示各个数据库连接。以下是两个数据源 (`dataSourceOne`, `dataSourceTwo`) 的示例: ```java @Configuration public class DataSourceConfig { @Bean(name = "dataSourceOne") @ConfigurationProperties(prefix = "spring.datasource.one") // 对应 application.yml 或 .properties 文件中的前缀 public DataSource dataSourceOne() { return DataSourceBuilder.create().build(); } @Bean(name = "dataSourceTwo") @ConfigurationProperties(prefix = "spring.datasource.two") // 对应另一个数据源的配置项 public DataSource dataSourceTwo() { return DataSourceBuilder.create().build(); } } ``` 上述代码片段展示了如何通过 `@ConfigurationProperties` 将不同数据源绑定到对应的配置文件字段上[^3]。 --- #### 创建动态数据源切换逻辑 为了支持动态切换数据源,需自定义一个 `DynamicDataSource` 类作为主数据源,并维护一组子数据源映射关系。当请求到达时,可以根据业务需求决定使用哪个具体的数据源。 ```java @Component public class DynamicDataSource extends AbstractRoutingDataSource { private final Map<Object, Object> dataSources; public DynamicDataSource(@Qualifier("dataSourceOne") DataSource dataSourceOne, @Qualifier("dataSourceTwo") DataSource dataSourceTwo) { this.dataSources = new HashMap<>(); this.dataSources.put("one", dataSourceOne); // 数据源名称 -> 数据源对象 映射 this.dataSources.put("two", dataSourceTwo); setTargetDataSources(this.dataSources); afterPropertiesSet(); // 初始化父类属性 } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); // 获取当前线程上下文中指定的数据源名 } } ``` 在此基础上还需要提供工具类用于存储和获取当前线程使用的数据源键值。 ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(String type) { contextHolder.set(type); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } } ``` --- #### 配置事务管理器 为了让 MyBatis 和 Spring 事务协同工作,必须引入合适的事务管理机制。通常情况下会为每个数据源单独注册一个事务管理器 (Transaction Manager),并通过 AOP 切面控制其作用范围。 ```java @Bean public PlatformTransactionManager transactionManagerOne( @Qualifier("dataSourceOne") DataSource dataSourceOne) { return new DataSourceTransactionManager(dataSourceOne); } @Bean public PlatformTransactionManager transactionManagerTwo( @Qualifier("dataSourceTwo") DataSource dataSourceTwo) { return new DataSourceTransactionManager(dataSourceTwo); } ``` 同时需要注意的是,MyBatis 提供了专门针对 Spring 的事务工厂类 `SpringManagedTransactionFactory` 来确保两者之间的紧密联系[^2]。 --- #### Mapper 接口扫描与 SQL Session 工厂构建 最后一步就是将这些准备工作串联起来形成完整的链路——即让 MyBatis 能够识别我们所定义好的多套数据源环境以及相应的事务处理策略。 ```java @Configuration @MapperScan(basePackages = "com.example.mapper.one", sqlSessionTemplateRef = "sqlSessionTemplateOne") public class MyBatisConfigOne { @Bean(name = "sqlSessionFactoryOne") public SqlSessionFactory sqlSessionFactoryOne( @Qualifier("dataSourceOne") DataSource dataSourceOne) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSourceOne); return factoryBean.getObject(); } @Bean(name = "sqlSessionTemplateOne") public SqlSessionTemplate sqlSessionTemplateOne( @Qualifier("sqlSessionFactoryOne") SqlSessionFactory sqlSessionFactoryOne) { return new SqlSessionTemplate(sqlSessionFactoryOne); } } ``` 重复以上过程以适配第二个或其他更多数量的数据源结构。 --- #### 总结 综上所述,整个方案的核心思想在于利用 Spring 提供的强大扩展能力定制化满足实际开发场景下的复杂需求;而 MyBatis 方面则侧重于无缝接入现有的生态体系之中从而发挥最大效能。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值