spring boot2.x + Druid动态数据源切换

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zl_momomo/article/details/82851134

数据源配置

<!-- alibaba的druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.9</version>
		</dependency>

创建用来保存数据源名字的线程安全的类。通过ThreadLocal栈封闭方式保证线程安全。

public class DataSourceContextHolder {
    public static final String Mater = "master";
    public static final String Slave1 = "slave1";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String name){
        contextHolder.set(name);
    }

    public static String getDataSource(){
        return contextHolder.get();
    }

    public static void cleanDataSource(){
        contextHolder.remove();
    }

}

实现AbstractRoutingDataSource并重写determineCurrentLookupKey()方法

public class DynamicDataSource extends AbstractRoutingDataSource {

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

AbstractRoutingDataSource类源码分析,通过resolvedDataSources中以map形式保存着多个数据源。通过重写的determineCurrentLookupKey()来动态的获得当前线程需要的数据源名字。

@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().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;
	}

配置其他基础数据源

@Configuration
public class MultipleDateSourceConfig {

    /**
     *
     * @return
     */
    @Bean("master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource creeateMasterDataSource(){
        return new DruidDataSource();
    }

    @Bean("slave1")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource creeateSlave1DataSource(){
        return new DruidDataSource();
    }

    /**
     * 设置动态数据源,通过@Primary 来确定主DataSource
     * @return
     */
    @Bean
    @Primary
    public DataSource createDynamicdataSource(@Qualifier("master") DataSource master,@Qualifier("slave1") DataSource slave1){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(master);
        //配置多数据源
        Map<Object, Object> map = new HashMap<>();
        map.put("master",master);
        map.put("slave1",slave1);
        dynamicDataSource.setTargetDataSources(map);
        return  dynamicDataSource;
    }
}

注意:

1.@Bean创建类的名字默认方法名,指定bean名字通过value或name属性。

2.createDynamicdataSource()中DataSource默认按类型注入,但是会报错。需要通过@Qualifier来指定注入bean的名字。往mybatis的sqlSessionFactory类注入DataSource是按类型注入。

3.通过@Primary注解来说明优先注入此bean。

上述的@ConfigurationProperties(prefix = "spring.datasource.master")注解从application.yml读取配置信息并注入

spring:
  datasource:
    #type: com.alibaba.druid.pool.DruidDataSource
    master:
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://127.0.0.1:3307/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: 123456
      name: master
      # 监控统计拦截的filters 有stat,wall,log4j
      filters: stat 
    slave1:
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: 123456
      name: slave1
      filters: stat

注意:添加filters的stat属性后,我们可以在localhost:8080/druid/sql.html 看到Druid对sql的分析管理信息

我们是手动配置DataSource,需要注解掉springboot的自动数据源配置类DataSourceAutoConfiguration 

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.test.mspringboot.mapper") //扫描mapper包
public class MSpringBootApplication {
	public static void main(String[] args) {
		SpringApplication.run(MSpringBootApplication.class, args);
	}
}

动态切换配置

通过自定义注解+AOP的方式来实现动态数据源的切换

引入jar

<!--aop-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.METHOD
})
public @interface DataSource {
    String value() default "master";
}

定义切面

@Aspect
@Component
@Order(1) //需要加入切面排序
public class DynamicDataSourceAspect {
    private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    //切入点只对@Service注解的类上的@DataSource方法生效
    @Pointcut(value="@within(org.springframework.stereotype.Service) && @annotation(dataSource)" )
    public void dynamicDataSourcePointCut(DataSource dataSource){}

    @Before(value = "dynamicDataSourcePointCut(dataSource)")
    public void switchDataSource(DataSource dataSource) throws Throwable{
        logger.info("##############数据源 :{}###############",dataSource.value());
        DataSourceContextHolder.setDataSource(dataSource.value());
    }

    @After(value="dynamicDataSourcePointCut(dataSource)")
    public void after(DataSource dataSource){
        DataSourceContextHolder.cleanDataSource();
    }
}

注意:需要使用@Order(1) 注解,保证数据源的切换在数据源的获取之前。

使用

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @DataSource(DataSourceContextHolder.Slave1)
    @Transactional
    @Override
    public User searchUserById(int id) throws Exception {
        return userMapper.selectByPrimaryKey(id);
    }

}

猜你喜欢

转载自blog.csdn.net/zl_momomo/article/details/82851134
今日推荐