springboot2.X动态数据源切换

springboot2.X+mybatis+druid

参考了网上多个版本的文章和代码,此demo作为记录

1、maven引用

springboot2.x+druid+mybatis+aop.

// An highlighted block
		<!-- druid -->
		<dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>druid-spring-boot-starter</artifactId>
		    <version>1.1.21</version>
		</dependency>
		<!-- org.apache.commons/commons-lang3 -->
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		    <version>3.9</version>
		</dependency>
		<!-- mybatis -->
		<dependency>
		    <groupId>org.mybatis.spring.boot</groupId>
		    <artifactId>mybatis-spring-boot-starter</artifactId>
		    <version>2.1.1</version>
		</dependency>
		<!-- AOP -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- mysql-connector -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

2、application.yml配置文件信息(多数据源)

spring:
  datasource:
    druid: 
      #stat-view-servlet:
        #enabled: true
        #login-username: admin
        #login-password: admin 
      master: 
        url: jdbc:mysql://localhost:3306/restzzpt?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
        username: root
        password: !!!!!!!
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource 
        #配置初始化大小、最小、最大 
        initial-size: 5
        min-idle: 2
        max-active: 100
        #配置获取连接等待超时的时间 
        max-wait: 60000
        #打开PSCache,并且指定每个连接上PSCache的大小
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        #
        test-on-borrow: true
        test-on-return: true
        test-while-idle: true
        #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 
        time-between-eviction-runs-millis: 30000
        #配置一个连接在池中最小生存的时间,单位是毫秒--max-evictable-idle-time-millis:
        min-evictable-idle-time-millis: 300000 
        #配置监控统计拦截的filters
        filters: stat,wall,slf4j
        #合并多个DruidDataSource的监控数据
        use-global-dataSource-stat: true
        remove-adandoned: true
      slave:
        url: jdbc:mysql://localhost:3306/restzzpt-1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
        username: root
        password: !!!!
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        #配置初始化大小、最小、最大 
        initial-size: 6
        min-idle: 3
        max-active: 101
        #配置获取连接等待超时的时间 
        max-wait: 60000
        #打开PSCache,并且指定每个连接上PSCache的大小
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        #
        test-on-borrow: true
        test-on-return: true
        test-while-idle: true
        #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 
        time-between-eviction-runs-millis: 30000
        #配置一个连接在池中最小生存的时间,单位是毫秒--max-evictable-idle-time-millis:
        min-evictable-idle-time-millis: 300000 
        #配置监控统计拦截的filters
        filters: stat,wall,slf4j
        #合并多个DruidDataSource的监控数据
        use-global-dataSource-stat: true
        remove-adandoned: true

3、注解类com.demo.annotation.DS

/**
 * 
 * @author zhao
 * @description 
 * 2020年4月5日
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
	String name() default "";
}

4、多数据源切换工具类

/**
 * 配置多数据源
 * 借助ThreadLocal类,通过ThreadLocal类传递数据源的参数
 * @author zhao
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
	
	public static final String  mysql= "master";
	public static final String  erpread= "slave";
	//本地线程,获取当前正在执行的currentThread 
	public static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 
	
	
	public static void setCustomerType(String customerType) {
		
		contextHolder.set(customerType);
	}
	
	
	public static String getCustomerType() {  
		
	    return contextHolder.get();       
	}   
	public static void clearCustomerType() {  
		contextHolder.remove();  
	} 
	@Override
	protected Object determineCurrentLookupKey() {
		return getCustomerType();  
	}
}

切面类:

/**
 *  多数据源,切面处理类
 * @author zhao
 * @description 
 * 2020年4月5日
 */
@Slf4j
@Aspect
@Order(-1)// 该切面应当先于 @Transactional 执行
@Component
public class DataSourceAspect{
	@Pointcut("@annotation(com.demo.annotation.DS)")
	public void dataSourcePointCut() {
		
	}; 
    /**
     * 切换数据源
     * @param point
     * @param dataSource
     */
    @Before("dataSourcePointCut()")
    public void switchDataSource(JoinPoint point) {
    	MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if(ds == null){
        	DynamicDataSource.setCustomerType(DynamicDataSource.mysql);
            log.debug("set datasource is " + DynamicDataSource.mysql);
        }else {
            DynamicDataSource.setCustomerType(ds.name());
            log.debug("set datasource is " + ds.name());
        }
    }

    /**
     * 重置数据源
     * @param point
     * @param dataSource
     */
    @After("dataSourcePointCut()")
    public void restoreDataSource(JoinPoint point) {
        // 将数据源置为默认数据源
    	DynamicDataSource.clearCustomerType();
    }
}

5、数据源信息注入

因多数据源配置,需要排除springboot默认加载类(启动类中加上@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)),需要手动在配置类中配置数据源信息以及mybatis配置信息。

@Configuration
@MapperScan("com.demo.dao")
public class DataSourceConfig {

	//注意:这里需要该注解声明是默认数据源
	@Primary
	@Bean(name="master",initMethod = "init")
	@ConfigurationProperties("spring.datasource.druid.master")
	public DataSource master(){
	    return DruidDataSourceBuilder.create().build();
	}
	@Bean(name="slave",initMethod = "init")
	@ConfigurationProperties("spring.datasource.druid.slave")
	public DataSource slave(){
	    return DruidDataSourceBuilder.create().build();
	}
	@Bean(name = "dynamicDataSource")
	public DataSource dynamicDataSource() {
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		// 默认数据源
		dynamicDataSource.setDefaultTargetDataSource(master());
		// 配置多数据源
		Map<Object, Object> dataBaseMap = new HashMap<>();
		dataBaseMap.put("master", master());
		dataBaseMap.put("slave", slave());
		dynamicDataSource.setTargetDataSources(dataBaseMap);
		return dynamicDataSource;
	}
	/**
	 * 为了做多数据源,将生成org.apache.ibatis.session.SqlSessionFactory的配置抽出到使用@Configuration修饰的配置类中。
	 * application.properties配置文件中关于驼峰转换的配置内容没有放进去
	 * 解决方案:将application.properties  mybatis的congfig加载到类中,再注入到SqlSessionFactoryBean中
	 * @return
	 */
//	@Bean
//	@ConfigurationProperties("mybatis.configuration")
//	public org.apache.ibatis.session.Configuration globalMybatisConfig(){
//		return new org.apache.ibatis.session.Configuration();
//	}
	@Bean(name="sqlServerSqlSessionFactory")
	public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource")DataSource dataSource) throws Exception {
		SqlSessionFactoryBean sessionFactory  = new SqlSessionFactoryBean();
		sessionFactory .setDataSource(dataSource);
		// 扫描Model
		sessionFactory.setTypeAliasesPackage("com.demo.*.entity");
		// 扫描映射文件
		sessionFactory .setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:dao/*.xml"));
		org.apache.ibatis.session.Configuration mybatisConf = new org.apache.ibatis.session.Configuration();
		//设置但JDBC类型为空时,某些驱动程序 要指定值,default:other
		mybatisConf.setJdbcTypeForNull(null);
		//[是否 启用  数据中 A_column 自动映射 到 java类中驼峰命名的属性 default:fasle] 
		mybatisConf.setMapUnderscoreToCamelCase(true);
		//解决 mybatis不返回查询结果为空的字段
		mybatisConf.setCallSettersOnNulls(true);
		sessionFactory .setConfiguration(mybatisConf);
//		sessionFactory .setConfiguration(globalMybatisConfig());
		return sessionFactory .getObject();
	}
	@Bean(name="sqlServerTransactionManager")//事务管理@Transactional(TransactionManager="mysqlTransactionManager")
	public PlatformTransactionManager platformTransactionManager(@Qualifier("dynamicDataSource")DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}
	@Bean(name="sqlServerSqlSessionTemplate")
	public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlServerSqlSessionFactory")SqlSessionFactory sqlSessionFactory) throws Exception {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
}

6、druid监控配置(可选)

参考druid官方文档:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

@Configuration
public class DruidConfig {


    @Bean
    @ConditionalOnMissingBean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //白名单
//        servletRegistrationBean.addInitParameter("allow", "192.168.6.195");
        //IP黑名单(存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
//        servletRegistrationBean.addInitParameter("deny", "192.168.6.73");
        //用于登陆的账号密码
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        //是否能重置数据
        servletRegistrationBean.addInitParameter("resetEnable", "true");
        return servletRegistrationBean;
    }
    /**
     * @Date: 2019/5/29 20:51
     * @Description: 注册filter信息,用于拦截
     */
    @Bean
    public FilterRegistrationBean<WebStatFilter> filterRegistrationBean() {
        //创建过滤器
        FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
        //设置过滤器过滤路径
        filterRegistrationBean.addUrlPatterns("/*");
        //忽略过滤得形式
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

7、在service实现层加上数据源注解:

@Service("userService")
@Transactional
public class ApiUserServiceImpl implements ApiUserService {

	@Autowired
	private ApiUserDao apiUserDao;
	
	@DS(name="master")
	@Override
	public ApiUserEntity queryByUsername(String username) {
		return apiUserDao.queryByUsername(username);
	}
	@DS(name="slave")
	@Override
	public ApiUserEntity queryByUsernameSlave(String username) {
		return apiUserDao.queryByUsername(username);
	}
	
	
}

测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicDataSourceTest {
    @Autowired
    private ApiUserService apiUserService;

    @Test
    public void test(){
        //数据源1
    	ApiUserEntity user1 = apiUserService.queryByUsername("13612345678");
        System.out.println(ToStringBuilder.reflectionToString(user1));

        //数据源2
        ApiUserEntity user2 = apiUserService.queryByUsernameSlave("13612345678");
        System.out.println(ToStringBuilder.reflectionToString(user2));
	    //数据源1
        //手动切换数据源,不用注解的方式。这个操作可以在controller层使用。
		//重点: 实际操作证明,切换的时候最好清空一下
        DynamicDataSource.clearCustomerType();
        //切换数据源,设置后 就OK了。可以随时切换过来(在controller层切换) 
        DynamicDataSource.setCustomerType(DynamicDataSource.mysql);
    	ApiUserEntity user3 = apiUserService.queryByUsername11("13612345678");
        System.out.println(ToStringBuilder.reflectionToString(user3));
    }

}

demo代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值