学习动态数据源切换,个人查了很多资料及demo,在此总结一下,只为加深印象,以及请各大佬指点。谢谢!!!
重点思想:动态数据源是基于Spring的AOP思想,在调用默认数据源之前切入实现的。
个人详情Demo,可移驾github:
https://github.com/WenFeiSun/SpringBoot
本人SpringBoot项目框架已经搭建完成。
第一步,导入MySQl,数据连接池依赖
<!--阿里数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.14</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.18</version> </dependency>
第二步,配置数据源
spring: #======datasource=========================================== #数据库连接配置信息 #spring.datasource.driverClassName = com.mysql.jdbc.Driver datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver druid: # 主库数据源 master: url: jdbc:mysql://localhost:3306/XIAO_QU?useUnicode=true&characterEncoding=UTF-8 username: root password: abc123 # 从库数据源 slave: # 从数据源开关/默认关闭 enabled: false url: username: password: # 初始连接数 initialSize: 5 # 最小连接池数量 minIdle: 10 # 最大连接池数量 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false webStatFilter: enabled: true statViewServlet: enabled: true # 设置白名单,不填则允许所有访问 allow: url-pattern: /druid/* # 控制台管理用户名和密码 login-username: login-password: filter: stat: enabled: true # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true 第三步,定义多数据源切面类
@Aspect //作用是把当前类标识为一个切面供容器读取 切面(Aspect @Order(1) //加载顺序 越小越提前加载 @Component public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.sun.control.web.common.annotation.DataSource)") //@annotation是针对方法的注解 切入点(Pointcut) public void dsPointCut() { } @Around("dsPointCut()")//当需要改变目标方法的返回值时,只能使用Around方法; public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); if (dataSource != null) { Object[] args = point.getArgs(); if (args.length > 0 && args[0] != null) { String dataSourceId = args[0].toString(); DynamicDataSourceContextHolder.setDataSourceType(dataSourceId); } } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } }
第四步,创建数据源上下文分配对象:DynamicDataSourceContextHolder
public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源的变量 */ public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } /** * 获得数据源的变量 */ public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } } 第五步,实现路由切换AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { /** * DynamicDataSourceContextHolder代码中使用setDataSourceType * 设置当前的数据源,在路由类中使用getDataSourceType进行获取, * 交给AbstractRoutingDataSource进行注入使用。 */ return DynamicDataSourceContextHolder.getDataSourceType(); } }
第六步,创建多数据源
@Component public class DataSourceConfig { @Autowired private DruidProperties druidProperties; @Autowired private DynamicDataSource dynamicDataSource; @Autowired private DataBaseDriverConfig dataBaseDriverConfig; @PostConstruct//项目启动既加载 public void loadCustDataSource(){ List<DsDatasource> dsDatasources = new ArrayList<>();//此处为数据源集合 DsDatasource dsDatasource1 = new DsDatasource(); dsDatasource1.setDatasourceId("sunwenfei123"); dsDatasource1.setDatabaseType(9); dsDatasource1.setDatabaseUrl("jdbc:mysql://localhost:3306/XIAO_QU1"); dsDatasource1.setDatabaseAccount("root"); dsDatasource1.setDatabasePassword("abc123"); dsDatasources.add(dsDatasource1); Map<Object, Object> targetDataSources = new HashMap<>(); for (DsDatasource dsDatasource : dsDatasources) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); String driverClassName = dataBaseDriverConfig.getDriver().get(dsDatasource.getDatabaseType() + ""); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(dsDatasource.getDatabaseUrl()); dataSource.setUsername(dsDatasource.getDatabaseAccount()); dataSource.setPassword(dsDatasource.getDatabasePassword()); DataSource ds = druidProperties.dataSource(dataSource); targetDataSources.put(dsDatasource.getDatasourceId(), ds); } dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); } }
最后要注意,启动类一定要加exclude = { DataSourceAutoConfiguration.class },要不然会出错。
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) //禁用数据源自动配置