springboot mybatis多数据源的两种整合方法

一个项目使用多个数据库(无论是主从复制--读写分离还是分布式数据库结构)的重要性变得越来越明显,整合的多数据源有两种方式:分包和aop。

分包

以分包的方式来区分不同的数据源,也就是不同的包,连接不同的数据库。

1、application的数据源配置

##数据源1
##driverClassName driver-class-name
spring.datasource.test1.driver-class-name: com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url:  jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.test1.username: root
spring.datasource.test1.password: root

#数据源2
spring.datasource.test2.driver-class-name: com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url:  jdbc:mysql://127.0.0.1:3306/springboot1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.test2.username: root
spring.datasource.test2.password: root

2、数据源配置类

DataSource1Config.java

@Configuration //注册到springboot 容器中
@MapperScan(basePackages = "com.jessDl.dataSource1", 
sqlSessionTemplateRef  = "test1SqlSessionTemplate")
public class DataSource1Config {

    @Bean(name = "test1DataSource")
    //数据源1的配置前缀
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    //由dataSource构建sqlSessionFactory
    @Bean(name = "test1SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(
@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //加载其他文件,如mapper.xml
       // bean.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath:mybatis/mapper/test1/*.xml"));
        return bean.getObject();
    }

    //事务管理
    @Bean(name = "test1TransactionManager")
    public DataSourceTransactionManager testTransactionManager(
@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test1SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

这里我们要对mybatis sqlSession有一些认识和理解,mybatis进行持久化时有几个重要的类:

(1)SqlSessionFactoryBuilder: build方法创建SqlSessionFactory实例。

(2)SqlSessionFactory: 创建SqlSession实例的工厂。

(3)SqlSession: 用于持久化操作的对象,类似于jdbc中的Connection。

(4)SqlSessionTemplate:  持久化层访问模板化的工具,线程安全,可通过构造参数或依赖注入。

一层层的注入,先创建DataSource,再创建SqlSessionFactory,再创建事务,最后包装到SqlSessionTemplate中。其中需要制定分库的mapper文件地址,以及分库dao层

DataSource2Config.java

@Configuration //注册到springboot 容器中
@MapperScan(basePackages = "com.jessDl.dataSource2", sqlSessionTemplateRef  = "test2SqlSessionTemplate")
public class DataSource2Config {

    @Bean(name = "test2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //加载其他文件,如mapper.xml
       // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
        return bean.getObject();
    }

    //事务管理
    @Bean(name = "test2TransactionManager")
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

dataSource1操作数据源1,dataSource2操作数据源2. dao喜忧参半和xml需要按照库来分在不同的目录。

aop方式

1、AbstractRoutingDataSource

实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,而AbstractRoutingDataSource又继承于AbstractDataSource,AbstractDataSource实现了统一的DataSource接口。

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }

}

这里我们编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解。

配置的多个数据源会放在AbstractRoutingDataSource的targetDataSources和defaultTargetDataSource中。

AbstractRoutingDataSource的getConnection()的方法的时候,先调用determinTargetDataSource()方法返回DataSource在进行getConnection()

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
}

很简单就可以看到,我们通过自己实现的determineCurrentLookupKey()方法返回了lookupKey,根据配置的key就获取到对应的数据源达到切换的功能。

2、yml配置

spring:
  datasource:
    druid:
      db1:
        url: jdbc:mysql://localhost:3306/eboot
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
      db2:
        url: jdbc:oracle:thin:@192.168.136.222:ORCL
        username: sa
        password: sa123456
        driver-class-name: oracle.jdbc.OracleDriver
        initialSize: 5
        minIdle: 5
        maxActive: 20
      db3:
        url: jdbc:oracle:thin:@192.168.136.223:ORCL
        username: sb
        password: sb123456
        driver-class-name: oracle.jdbc.OracleDriver
        initialSize: 5
        minIdle: 5
        maxActive: 20

3、pom依赖

<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.9</version>
		</dependency>

4、把数据源放入DruidDataSource中

@EnableTransactionManagement
@Configuration
public class DataSourceConfig {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);

    @Bean(name="db1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
    public DataSource db1(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name="db2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DataSource db2(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
                                         @Qualifier("db2") DataSource db2){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object,Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.PG1.getName(),db1);
        targetDataSources.put(DataSourceType.PG2.getName(),db2);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(db1);
        return dynamicDataSource;
    }
}

5、为了规范,DataSourceType枚举类

public enum DataSourceType {
    PG1("pg1"),
    PG2("pg2");

    private String name;

    DataSourceType(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }
}

6、动态数据决策类

public class DynamicDataSource extends AbstractRoutingDataSource {

    //此方法用于产生要选取的数据源逻辑名称
    @Override
    protected Object determineCurrentLookupKey(){
        return BaseContextHandler.get(EvaConstants.DATA_SOURCE);
    }
}

7.ThreadLocal类

public class BaseContextHandler {
    private final static Logger logger = LoggerFactory.getLogger(BaseContextHandler.class);

    public static ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<>();

    public static void set(String key,Object value){
        Map<String,Object> map = threadLocal.get();
        if(map==null){
            map = new HashMap<String,Object>();
            threadLocal.set(map);
        }
        map.put(key,value);
    }

    public static Object get(String key){
        Map<String,Object> map = threadLocal.get();
        if(map==null){
            map = new HashMap<String,Object>();
            threadLocal.set(map);
        }
        return map.get(key);
    }

    public static void remove(){
        threadLocal.remove();
    }
}

8、AOP

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
    DataSourceType value() default DataSourceType.PG1;
}
@Aspect
@Order(2)
@Component
public class DataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource){
        DataSourceType dbType = targetDataSource.value();
        if(dbType.equals(DataSourceType.PG1)){
            BaseContextHandler.set(EvaConstants.DATA_SOURCE,DataSourceType.PG1.getName());
        }else{
            BaseContextHandler.set(EvaConstants.DATA_SOURCE,DataSourceType.PG2.getName());
        }
    }

    @After("@annotation(targetDataSource)")
    public void clearDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
        BaseContextHandler.remove();
    }
}

这样在dao层只需要这样

 @TargetDataSource(value= DataSourceType.PG2)
    int getSeq(@Param("seqName")String seqName);

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值