SpringBoot项目配置多数据源的两种方式

前提项目架构:SpringBoot+mybatis-plus-Druid

方式一、基于dynamic-datasource-spring-boot-starter实现(配置简单)

1、添加依赖:

<!--引入多数据源需要的依赖包-->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>3.1.1</version>
</dependency>

2、在application.yml中配置多数据源

spring:
  application:
    name: zhangjiadayuan-server
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # Druid的其他属性配置
    druid:
      # 初始化时建立物理连接的个数
      initial-size: 10
      # 连接池的最小空闲数量
      min-idle: 5
      # 连接池最大连接数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-eviction-runs-millis: 60000
      # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
      min-evictable-idle-time-millis: 30000
      # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
      validation-query: SELECT 1 FROM DUAL
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
      pool-prepared-statements: false
      # 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入
      filters: stat,wall,slf4j
      # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
      max-pool-prepared-statement-per-connection-size: -1
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      web-stat-filter:
        # 是否启用StatFilter默认值true
        enabled: true
        # 添加过滤规则
        url-pattern: /*
        # 忽略过滤的格式
        exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico

      stat-view-servlet:
        # 是否启用StatViewServlet默认值true
        enabled: true
        # 访问路径为/druid时,跳转到StatViewServlet
        url-pattern: /druid/*
        # 是否能够重置数据
        reset-enable: false
        # 需要账号密码才能访问控制台,默认为root  http://localhost:8585/druid/
        login-username: root
        login-password: 123456
        # IP白名单
        allow: 127.0.0.1
        # IP黑名单(共同存在时,deny优先于allow)
        deny:
    dynamic:
      primary: master #设置默认的数据源或者数据源组
      strict: false #严格匹配数据源,默认为false,true未匹配到数据源时会抛出异常,false使用默认数据源
      datasource:
        master:
          # 数据源基本配置
          username: root
          password: root123
          url: jdbc:mysql://127.0.0.1:3306/zhangjiadayuan?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          # driver-class需要注意mysql驱动的版本(com.mysql.cj.jdbc.Driver 或 com.mysql.jdbc.Driver)
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          # 数据源基本配置
          username: root
          password: root123
          url: jdbc:mysql://127.0.0.1:3306/zhangjiadayuan2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          # driver-class需要注意mysql驱动的版本(com.mysql.cj.jdbc.Driver 或 com.mysql.jdbc.Driver)
          driver-class-name: com.mysql.cj.jdbc.Driver

3、排除掉Druid的数据源自动配置类

在启动类中需要排除掉DruidDataSourceAutoConfigure.class,就是取消Druid的数据源的自动配置类。

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;


@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@ComponentScan("cn.zhang.*")
@MapperScan("cn.zhang.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

4、切换数据源 

使用@DS("数据源名字")切换数据源,@DS注解可以用在方法上或类上,同时存在就近原则,方法上注解优先于类上;没有使用@DS默认使用默认数据源。

方式二、基于自定义注解

1、配置文件配置多数据源

spring:
  datasource:
    druid:
      db1:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
        username: root
        password: 123456
      db2:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
        username: root
        password: 123456
      test-on-borrow: true

2、定义数据源配置类

该类的作用就是初始化数据源DataSource实例,以及初始化SqlSessionFactory实例。这里需要注意的是必须使用MybatisSqlSessionFactoryBean来获取会话工厂SqlSessionFactory,不然的话,baseMapper中的生成动态SQL的方法就不能使用了。

@Configuration(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfigurer {
    //mapper模式下的接口层
    static final String BASE_PACKAGE = "cn.zhang.mapper";
	public static final String CLASSPATH_MAPPING_XML = "classpath*:mapper/*.xml";
    /**
     * 配置数据源
     *
     * @return
     */
    @Bean(name = "db1")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
    public DataSource db1() {
        return DruidDataSourceBuilder.create().build();
    }
    /**
     * 配置数据源
     *
     * @return
     */
    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DataSource db2() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<Object, Object>(2);
        dataSourceMap.put(DataSourceKey.Master.getName(), db1());
        dataSourceMap.put(DataSourceKey.SLAVE.getName(), db2());
        dynamicRoutingDataSource.setDefaultTargetDataSource(dcs());
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
        DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
        return dynamicRoutingDataSource;
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        //MybatisPlus使用的是MybatisSqlSessionFactory
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //此处设置为了解决找不到mapper文件的问题
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(CLASSPATH_MAPPING_XML));
        return sqlSessionFactoryBean.getObject();
    }
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }
    /**
     * 事务
     *
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
/**
 * 该类继承自 AbstractRoutingDataSource 类,
 * 在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        logger.info("current datasource is : {}", DynamicDataSourceContextHolder.getDataSourceKey());
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}
public class DynamicDataSourceContextHolder {
    private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.DCS.getName());
    public static List<Object> dataSourceKeys = new ArrayList<Object>();
    public static void setDataSourceKey(String key){
        CONTEXT_HOLDER.set(key);
    }
    public static Object getDataSourceKey(){
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceKey(){
        CONTEXT_HOLDER.remove();
    }
    public static Boolean containDataSourceKey(String key){
        return dataSourceKeys.contains(key);
    }
}
public enum DataSourceKey {
	MASTER("db1"),
	SLAVE("db2");
	
	private String name;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	private DataSourceKey(String name) {
		this.name = name;
	}
}

3、自定义注解和定义切面

//该注解只是作用在方法上,这里默认的数据源是DB1
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value() default "db1";
}

 切面顾名思义就是拦击标注自定义注解@TargetDataSource注解的方法,并且根据注解指定的数据源的key切换数据源。

@Aspect
@Component
public class DynamicDataSourceAspect {
    private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    @Before("@annotation(targetDataSource)")
    public void switchDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
        if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value().getName())) {
            logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value());
        } else {
            DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value().getName());
            logger.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
        }
    }
    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
        DynamicDataSourceContextHolder.clearDataSourceKey();
        logger.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
    }
}

4、使用切换数据源

//也可用与Service方法或类上
@TargetDataSource("db2")
String getClassStudent(@Param("open_id") String openId);

猜你喜欢

转载自blog.csdn.net/weixin_50003028/article/details/134570523