下面我就来详细讲解一下“Spring多租户数据源管理 AbstractRoutingDataSource”的完整攻略。
什么是多租户数据源管理
在多租户系统中,一份应用程序服务多个用户,每个用户有自己独立的数据。多租户数据源解决了这个问题,通过配置多个数据源,根据不同的用户请求来动态选取对应的数据源,从而实现对多租户数据的支持。
AbstractRoutingDataSource
Spring提供了一个实现多租户数据源管理的类AbstractRoutingDataSource,它是一个抽象类,通过继承该类并实现determineCurrentLookupKey方法,就能实现自己的动态数据源。
determineCurrentLookupKey()方法作用是获取当前的数据源key,然后根据key去获取对应的数据源,每个key对应一个对应的数据源。
具体来说,动态数据源管理需要以下两步:
-
在项目中,自定义一个类继承AbstractRoutingDataSource;
-
实现determineCurrentLookupKey()方法,该方法根据请求参数、session或者其他规则来确定对应的数据源key。
简单来说,AbstractRoutingDataSource 负责管理一组数据源,并提供了一种机制,使得在访问数据源时,能够动态选择其中的一个来使用。
AbstractRoutingDataSource代码示例
下面是一个使用 AbstractRoutingDataSource 的示例代码。
- 自定义类继承 AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
- 配置 AbstractRoutingDataSource
@Configuration
public class DynamicDataSourceConfig {
@Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
// 配置 targetDataSources,key 为数据源 key,value 为对应的数据源 DataSource
targetDataSources.put("data1", dataSource1());
targetDataSources.put("data2", dataSource2());
dynamicDataSource.setTargetDataSources(targetDataSources);
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
return dynamicDataSource;
}
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
}
在上面的示例中,我们定义了一个 DynamicDataSource 类,它继承了 AbstractRoutingDataSource 类,然后实现了该类的 determineCurrentLookupKey() 抽象方法。在该方法中,我们使用了一个名为"DataSourceContextHolder"的类来获取数据源 key。
DataSourceContextHolder是一个自定义的类,它使用 ThreadLocal 来存储当前线程使用的数据源 key。当需要切换数据源时,只需要调用 DataSourceContextHolder.setCurrentDataSource(key) 方法即可。
另外,我们在 DynamicDataSourceConfig 配置类中,定义了 dataSource1 和 dataSource2 两个数据源,并将它们注册到了动态数据源 DynamicDataSource 中。
AbstractRoutingDataSource示例2
下面是一个更加详细的示例,它演示了如何使用 Annotation 实现动态数据源的在不同方法中进行切换。
1. 添加注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "data1";
}
2. 自定义类继承 AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
3. 自定义ContextHolder类
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
4. 切面
@Aspect
@Component
public class DynamicDataSourceAspect {
@Around("@annotation(ds)")
public Object around(ProceedingJoinPoint point, DataSource ds) throws Throwable {
String datasourceKey = ds.value();
if (!DynamicDataSourceContextHolder.containsDataSource(datasourceKey)) {
System.err.println("数据源[{}]不存在,使用默认数据源 > {}" + ds.value() + point.getSignature());
} else {
System.out.println("Use DataSource : {} > {}"+ ds.value() + point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(datasourceKey);
}
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
System.out.println("清空数据源信息 > {}");
}
}
}
5. 工具类
public class DataSourceEnum {
public enum DataSourceType {
DataSource1("data1"),
DataSource2("data2");
private String type;
DataSourceType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
public static List<String> getDataSourceIds() {
return dataSourceIds;
}
public static void addDataSourceIds(String dataSourceId) {
LOG.info("Register datasource : {}", dataSourceId);
dataSourceIds.add(dataSourceId);
}
public static void addDataSourceIds(Collection<String> dataSourceIds) {
DynamicDataSourceContextHolder.dataSourceIds.addAll(dataSourceIds);
}
public static void removeDataSourceIds(String dataSourceId) {
LOG.info("Remove datasource : {}", dataSourceId);
dataSourceIds.remove(dataSourceId);
}
public static void clearDataSourceIds() {
dataSourceIds.clear();
}
public static void setDataSourceIds(Collection<String> dataSourceIds) {
clearDataSourceIds();
addDataSourceIds(dataSourceIds);
}
}
6. 测试代码
@SpringBootTest(classes = Springboot2MybatisplusApplication.class)
@RunWith(SpringRunner.class)
public class DynamicDataSourceTest {
@Autowired
private DynamicDataSourceMapper mapper;
@Test
public void testInsert() {
User user = new User();
user.setName("name");
userMapper.insert(user);
}
@Test
@DataSource(DataSourceEnum.DataSourceType.DataSource1)
public void testInsertWithDataSource1() {
User user = new User();
user.setName("name");
userMapper.insert(user);
}
@Test
@DataSource(DataSourceEnum.DataSourceType.DataSource2)
public void testInsertWithDataSource2() {
User user = new User();
user.setName("name");
userMapper.insert(user);
}
}
在上面的示例代码中,我们使用了两个数据库:DataSource1 和 DataSource2。在使用 DynamicDataSourceContextHolder 类的 addDataSourceIds 方法注册需要使用的 DataSource 的 key。
在测试代码中,我们通过使用已经定义好的 DataSource 注解,告诉 Spring 当前需要使用哪个数据源。比如,在 testInsertWithDataSource1 方法上添加注解@DataSource(DataSourceEnum.DataSourceType.DataSource1),那么在该方法执行时,就会使用 DataSource1 数据源。
总结
这就是使用 AbstractRoutingDataSource 实现动态数据源的完整攻略。通过使用此方法,可以支持在一个应用程序中使用多个数据库,并且允许在应用程序运行时切换不同的数据源,从而实现多租户数据源管理的功能。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring多租户数据源管理 AbstractRoutingDataSource - Python技术站