SpringBoot多数据源配置

最近项目用到了spring多数据源配置,恰好看到这篇好文章,特地分享下

1 SpringBoot分库配置

主要介绍两种整合方式,分别是 springboot+mybatis 使用分包方式整合,和 springboot+druid+mybatisplus 使用注解方式整合

1.1 准备数据

在本地新建两个数据库,名称分别为db1db2,新建一张user表,表结构如下

image.png

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(25) NOT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  `sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别:0-男,1-女',
  `addr` varchar(100) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试用户表'

1.2 springboot+mybatis使用分包方式整合

1.2.1 pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.9.RELEASE
         
    
    com.example
    multipledatasource
    0.0.1-SNAPSHOT
    multipledatasource
    Demo project for Spring Boot

    
        1.8
    
   
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
         
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.1.0
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

1.2.2 application.yml 配置文件

server:
  port: 8080 # 启动端口
spring:
  datasource: 
    db1: # 数据源1
      jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    db2: # 数据源2
      jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

注意事项:
各个版本的 springboot 配置 datasource 时参数有所变化,例如低版本配置数据库 url时使用 url 属性,高版本使用 jdbc-url 属性,请注意区分

1.2.3 连接数据源配置文件

1.2.3.1 连接源配置一

@Configuration
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceConfig1 {

    @Primary // 表示这个数据源是默认数据源, 这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
    @Bean("db1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db1") //读取application.yml中的配置参数映射成为一个对象
    public DataSource getDb1DataSource(){
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean("db1SqlSessionFactory")
    public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致)
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db1/*.xml"));
        return bean.getObject();
    }

    @Primary
    @Bean("db1SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

1.2.3.2 连接源配置二

@Configuration
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class DataSourceConfig2 {

    @Bean("db2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource getDb1DataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean("db2SqlSessionFactory")
    public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db2/*.xml"));
        return bean.getObject();
    }

    @Bean("db2SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

1.2.4 项目结构

image.png

注意事项:
service 层中根据不同的业务注入不同的 dao
如果是主从复制- -读写分离:比如 db1 中负责增删改,db2 中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

1.3 springboot+druid+mybatisplus使用注解整合

1.3.1 pom.xml



   4.0.0
   
       org.springframework.boot
       spring-boot-starter-parent
       2.1.9.RELEASE
        
   
   com.example
   mutipledatasource2
   0.0.1-SNAPSHOT
   mutipledatasource2
   Demo project for Spring Boot

   
       1.8
   

   
       
           org.springframework.boot
           spring-boot-starter-web
       
       
           com.baomidou
           mybatis-plus-boot-starter
           3.2.0
       
       
           com.baomidou
           dynamic-datasource-spring-boot-starter
           2.5.6
       
       
           mysql
           mysql-connector-java
           runtime
       
       
           com.alibaba
           druid-spring-boot-starter
           1.1.20
       
       
           org.projectlombok
           lombok
           true
       
       
           org.springframework.boot
           spring-boot-starter-test
           test
       
   

   
       
           
               org.springframework.boot
               spring-boot-maven-plugin
           
       
   

   
       
           local1
           
               local1
           
           
               true
           
       
       
           local2
           
               local2
           
       
   

1.3.2 application.yml 配置文件

server:
  port: 8080
spring:
  datasource:
    dynamic:
      primary: db1 # 配置默认数据库
      datasource:
        db1: # 数据源1配置
          url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        db2: # 数据源2配置
          url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
      durid:
        initial-size: 1
        max-active: 20
        min-idle: 1
        max-wait: 60000
  autoconfigure:
    exclude:  com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 去除druid配置

DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,其会在原生的spring.datasource下找 url, username, password 等。动态数据源 URL 等配置是在 dynamic 下,因此需要排除,否则会报错。排除方式有两种,一种是上述配置文件排除,还有一种可以在项目启动类排除

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

1.3.3 使用@DS区分数据源

给使用非默认数据源添加注解@DS
@DS可以注解在 方法 上和 上,同时存在方法注解优先于类上注解。
注解在 service 实现或 mapper 接口方法上,不要同时在 servicemapper 注解

mapper上使用

@DS("db2") 
public interface UserMapper extends BaseMapper {
}

service上使用

@Service
@DS("db2")
public class ModelServiceImpl extends ServiceImpl implements IModelService {}

方法上使用

@Select("SELECT * FROM user")
@DS("db2")
List selectAll();

转载于:https://www.cnblogs.com/aizen-sousuke/p/11756279.html

1.3.4 @Transaction和@DS问题

点击了解当一起使用@Transaction和@DS时@DS失效问题

1.4 读写分离库

读写分离库使用AbstractRoutingDataSource
AbstractRoutingDataSource类和aop结合,还可以用来作为读写分离库

1.4.1 AbstractRoutingDataSource原理

AbstractRoutingDataSource原理:

  1. Map targetDataSources 保存了所有可能的数据源,key为数据库的keyvalueDataSource对象或字符串形式的连接信息
  2. Object defaultTargetDataSource 保存了默认的数据源,用于找不到具体的数据源时使用
  3. afterPropertiesSet() 方法
    解析targetDataSources数据源信息成的形式,保存在Map resolvedDataSources
    defaultTargetDataSource中的默认数据源信息解析成 DataSource对象保存在 DataSource resolvedDefaultDataSource
  4. determineCurrentLookupKey() 提供给子类重写,指定当前线程使用的具体的数据源的key
  5. determineTargetDataSource() 根据 determineCurrentLookupKey()方法返回的key 返回数据源DataSouce对象,若没有,则使用默认数据源对象
  6. getConnection() 根据determineTargetDataSource()返回的数据源,与其建立连接

1.4.2 数据源分离配置application.properties

server.port=8081
spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-1?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

y2021.spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-2?useUnicode=true&characterEncoding=utf8
y2021.spring.datasource.username=root
y2021.spring.datasource.password=*****
y2021.spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

logging.level.com.ijianghu.dynamic.web=debug

1.4.3 配置数据源和事务管理

@Configuration
public class BaseDataSourceConfig {

    /**
     * 配置默认数据源,并设置高优先级
     * @return
     */
    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置多数据源
     * @return
     */
    @Bean("y2021DataSource")
    @ConfigurationProperties(prefix = "y2021.spring.datasource")
    public DataSource y2021DataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 设置多数据源,配置动态路由数据源
     * @return
     */
    @Bean("dynamincDataSource")
    public DataSource dynamincDataSource(){
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        HashMap dataSourceMap = new HashMap<>();
        dataSourceMap.put("dataSource",dataSource());
        dataSourceMap.put("y2021DataSource",y2021DataSource());
        //配置默认目标数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource());
        //设置目标数据源集合,供路由选择
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
        return dynamicRoutingDataSource;
    }

    /**
     * 设置会话工厂
     * @return
     * @throws Exception
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //配置数据源为多数据源
        factoryBean.setDataSource(dynamincDataSource());
        //设置mybaits的xml文件路径
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();
    }

    /**
     * 配置事务管理器
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dynamincDataSource());
    }

}

1.4.4 继承AbstractRoutingDataSource类,实现选择目标数据源

/**
 * Multiple DataSource Configurer
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected Object determineCurrentLookupKey() {           
        logger.info("Current DataSource is {}",DynamicDataSourceContextHolder.getDataSourceKey());
        //从动态数据源上下文持有者里面获取
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

1.4.5 配置多数据源上下文持有者

public class DynamicDataSourceContextHolder {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * Maintain variable for every thread, to avoid effect other thread
     */
    private static ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;
    /**
     * All DataSource List
     */
    public static List dataSourceKeys = new ArrayList<>();
    /**
     * Get current DataSource
     * @return data source key
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

  
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    public static void useSlaveDataSource(String ds) {
        DataSourceKey dataSourceKey = DataSourceKey.valueOf(ds);
        //if there is no suitable enum,then use default dataSource
        if(dataSourceKey == null){
            setDataSourceKey(DataSourceKey.dataSource.dataSourceName);
        }
        setDataSourceKey(dataSourceKey.dataSourceName);
    }

    public static void useMasterDataSource() {
        CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
    }

}

DataSourceKey类

public enum DataSourceKey {
    dataSource("dataSource","dataSource"),
    y2021("key","y2021DataSource");

    public String key;
    public String dataSourceName;
    DataSourceKey(String key, String dataSourceName){
        this.key = key;
        this.dataSourceName = dataSourceName;
    }
}

1.4.6 配置AOP切面

@Aspect
@Order(-1)//Order中的数字代表启动优先级,-1是比0还有更高的优先级
@Component
public class DynamicDataSourceAspect {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    //@annotation用于匹配当前执行方法持有指定注解的方法
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void daoAspect(){ }


    @Before("daoAspect()")
    public void switchDataSource(JoinPoint point){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ds = request.getHeader("ds");
        if(ds != null){
            DynamicDataSourceContextHolder.useSlaveDataSource(ds);
        }else{
            DynamicDataSourceContextHolder.useMasterDataSource();
        }
    }
}

你可能感兴趣的:(SpringBoot多数据源配置)