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

阅读数:247 评论数:0

跳转到新版页面

分类

python/Java

正文

一、概述

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

1、SqlSessionTemplate

SqlSessionTemplate是Mybatis—Spring的核心,是用来代替默认Mybatis实现的DefaultSqlSessionFactory,也可以说是DefaultSqlSessionFactory的优化版。SqlSessionTemplate是线程安全的。

(1)依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

2、DataSourceTransactionManager

对JDBC事务进行管理,事务可以参见 :springboot事务详解

SqlSession等可以参考:mybatis调用链路

二、分包

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

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进行持久化时有几个重要的类:

SqlSessionFactoryBuilder build方法创建SqlSessionFactory实例。
SqlSessionFactory 创建SqlSession实例的工厂。
SqlSession 用于持久化操作的对象,类似于jdbc中的Connection。
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);



相关推荐

一、概述 1、读写分离 数据库主节点压力比较大,需要增加从节点提供读操作,以减少压力。 2、多数据源 一个复杂发项目,因为没有拆分服务,需要连接多个业务的数据源。 这些场景都需要使用springboo

MyBatis Plus官方文档<

一、项目依赖 <dependencies> <!-- --> <dependency> <groupId>org.springframework

方法一 给XXMapper.java加上Mapper注解,如: import org.apache.i

一、request uri部分 @PathVariable 获取路径参数,形如url/{id} 二、request header部分 @RequestHeade

出现这个错误,是因为mybatis默认OGNL解析参数,所以会自动采用对象树形式取String.xxx值。 解决方法: 方法1:在方法中提前定义 <pre c

&lt;if test="extraSql != null and extraSql !=''"&gt; AND ${ext

一、概述 mybatis原来是apache的一个开源项目,叫做ibatis,2010年由apache迁移到了google code,并且改名为mybatis。2013年迁移到github。 mybat

(1)Executor 执行增删改查操作

实际开发中,有时候需要把当前插入的数据id取出来,但又不想再去查一遍. mybatis提供了两种返回insert方法后的主键的方法 : 1、根据useGeneratedKeys获取