关键类
ThreadLocal
线程局部变量,所谓局本部变量,就是仅仅能在本线程访问,不能在线程之间进行共享访问的变量AbstractRoutingDataSource
getConnection()根据查找lookup key键对不同目标数据源的调用,通常通过某些线程的事务上下文来实现
逻辑思路
- DynamicDataSource继承AbstractDataSource类,并实现了determineCurrentLookupKey()方法。
- 我们配置的多个数据源会放在AbstractRoutingSource的tartgetDataSource和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
- AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回在DataSource在进行getConnection().
实现多数据源
步骤一
在springboot中,增加多数据源配置
DataSource: master 、slave
yml配置步骤二
扩展Spring的AbstractRoutingDataSource抽象模块类,AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现多数据源的核心,并对方法返回DataSource在进行getConnection(),决定数据源是哪一个步骤三
配置DataSource,指定数据源信息。步骤四
通过注解,实现多数据源
@userDataSource("master") --> aop --> master --> threadlocal put key->master步骤五
配置加上(exclude={DataSourceAutoConfiguration.class})
关于事务
只支持单库事务,也就是说切换数据源要在开启事务之前执行
实现
yml配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
first: #数据源1
url: jdbc:mysql://localhost:3306/test1?...
username: root
password: root
second: #数据源2
url: jdbc:mysql://localhost:3306/test2?...
username: root
password: root
相关类
DynamicDataSource:动态获取数据源的实现,继承AbstractRoutingDataSource(每执行一次数据库,动态获取DataSource)
DynamicDataSourceContextHolder:动态数据源上下文管理,相当于在容器中管理数据源实例
DynamicDattaSourceAspect:动态数据源通知
TargetDataSource:数据源注解,作用于类、接口或者方法上,用于指定数据源
DynamicDatasourceConfig:动态数据源配置,实例化所有配置数据源
DataSourceNames 定义数据源名称
代码实现
-
DataSourceNames
public interface DataSourceNames { String FISRST = "first"; String SECOND = "second"; }
-
DynamicDataSourceContextHolder
@Slf4j public class DynamicDataSourceContextHolder { /** * 存放当前线程使用的数据源类型信息 */ private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * 存放数据源ID,即数据源名称 */ public static List<String> dataSourceIds = new ArrayList<String>(); /** * 设置数据源 * * @param dataSourceType */ public static void setDataSourceType(String dataSourceType) { log.info("添加数据源实例到管理器中,dataSourceType{}", dataSourceType); contextHolder.set(dataSourceType); } /** * 获取数据源 * * @return */ public static String getDataSourceType() { log.info("从数据源实例管理器中获取当前实例"); return contextHolder.get(); } /** * 清除数据源 */ public static void clearDataSourceType() { log.info("清除当前数据源实例"); contextHolder.remove(); } }
-
DynamicDataSourceConfig
@Configuration public class DynamicDataSourceConfig { @Bean(name = "first") @ConfigurationProperties(prefix = "spring.datasource.druid.first") public DataSource firstDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean(value = "second") @ConfigurationProperties(prefix = "spring.datasource.druid.second") public DataSource SecondDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource(@Qualifier("first") DataSource firstDataSource, @Qualifier("second") DataSource secondDataSource){ DynamicDataSource multipleDataSource = new DynamicDataSource(); Map<Object,Object> targetDataSources = new HashMap<>(2); targetDataSources.put(DataSourceNames.FISRST,firstDataSource); targetDataSources.put(DataSourceNames.SECOND,secondDataSource); // 添加数据源 multipleDataSource.setTargetDataSources(targetDataSources); // 设置默认数据源 multipleDataSource.setDefaultTargetDataSource(firstDataSource); return multipleDataSource; } }
-
DynamicDataSource
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
-
DataSourceAspect
@Slf4j @Aspect @Component @Order(-1) public class DataSourceAspect { /** * 改变数据源,判断使用注解中的数据源实例名称,根据实例名称从上下文管理器中获取数据源 * @param joinPoint * @param targetDataSource */ @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { log.info("选择数据源---" + targetDataSource.value()); DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value()); } /** * 使用完后清理数据源 * @param joinPoint * @param targetDataSource */ @After("@annotation(targetDataSource)") public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { log.debug("清除数据源 " + targetDataSource.value() + " !"); DynamicDataSourceContextHolder.clearDataSourceType(); } }
-
TargetDataSource
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface TargetDataSource { // 默认数据源为第一个 String value() default DataSourceNames.FISRST; }
-
使用
@Service public class DataSourceTestService { @Autowired private UserService userService; public List<User> test1(){ return userService.listUsers(); } @TargetDataSource(DataSourceNames.SECOND) public List<User> test2(){ return userService.listUsers(); } }
-
测试
@RunWith(SpringRunner.class) @SpringBootTest class DynamicDataSourceTest { @Autowired private DataSourceTestService dataSourceTestService; @Test public void test1(){ // 数据源一 List<User> userList1 = dataSourceTestService.test1(); System.out.println(userList1); // 数据源二 List<User> userList2 = dataSourceTestService.test2(); System.out.println(userList2); } }