摘要: 本文将介绍如何使用Mybatis-Plus配合Spring Boot来实现动态多数据源的切换。我们将讨论依赖引入、配置修改、自定义数据源提供者、配置类编写、数据源工具类实现以及AOP切面编程的应用。
一、引言
在现代的企业级应用中,常常需要根据不同的业务场景动态地切换数据源。Mybatis-Plus提供了一个基于Spring Boot的快速集成多数据源的启动器,dynamic-datasource-spring-boot-starter
,以简化这一过程。
二、依赖引入
首先,在项目的pom.xml
文件中引入所需的依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
三、修改yml
配置
接下来,在application.yml
件中配置数据源:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
dynamic:
primary: master
strict: false
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/master_db
username: root
password: pass
四、实现自定义数据源提供者
我们可以通过继承AbstractJdbcDataSourceProvider
类来实现自定义的数据源提供者:
public class CustomDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider {
public CustomDynamicDataSourceProvider(String driverClassName, String url, String username, String password) {
super(driverClassName, url, username, password);
}
@Override
protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
Map<String, DataSourceProperty> map = new HashMap<>();
ResultSet rs = statement.executeQuery(GlobalConstant.DB_QUERY);
/**
* 获取信息
*/
while (rs.next()) {
String dbName = rs.getString("db_name");
String dbIp = rs.getString("db_ip");
String dbIpPort = rs.getString("db_ip_port");
String jdbcUrl = GlobalConstant.DB_URL
.replace("{dbIp}", dbIp)
.replace("{dbPort}", dbIpPort)
.replace("{dbName}", dbName);
String dbUser = rs.getString("db_user");
String dbPwd = rs.getString("db_pwd");
String key = rs.getString("id");
String name = rs.getString("name");
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty
.setDriverClassName(GlobalConstant.DB_DRIVER)
.setUrl(jdbcUrl)
.setUsername(dbUser)
.setPassword(dbPwd)
.setPoolName(name);
map.put(key, dataSourceProperty);
}
return map;
}
}
五、添加DataSourceConfiguration
配置类
@Primary
@Configuration
public class DataSourceConfiguration {
@Autowired
private DynamicDataSourceProperties properties;
@Value("${spring.datasource.dynamic.primary}")
private String masterName;
@Bean
public DynamicDataSourceProvider customDynamicDataSourceProvider() {
Map<String, DataSourceProperty> datasource = properties.getDatasource();
DataSourceProperty property = datasource.get(masterName);
return new CustomDynamicDataSourceProvider(property.getDriverClassName(), property.getUrl(), property.getUsername(), property.getPassword());
}
}
六、实现数据源工具类DataSourceService
@Service
public class DataSourceService {
@Autowired
private DynamicRoutingDataSource dataSource;
@Autowired
private HikariDataSourceCreator dataSourceCreator;
public DataSource get(String key){
return dataSource.getDataSource(key);
}
public Set<String> getList(){
return dataSource.getDataSources().keySet();
}
public Set<String> add(DataSourceProperty dsp, String key) {
dsp.setDriverClassName(GlobalConstant.DB_DRIVER);
DataSource creatorDataSource = dataSourceCreator.createDataSource(dsp);
dataSource.addDataSource(key, creatorDataSource);
return dataSource.getDataSources().keySet();
}
public Boolean remove(String name) {
dataSource.removeDataSource(name);
return Boolean.TRUE;
}
}
七、通过AOP动态切换数据源
最后,我们使用AOP来实现数据源的动态切换:
@Slf4j
@Aspect
@Component
public class DataSourceAspect {
@Autowired
private DataSourceService sourceService;
@Pointcut("within(com.baomidou.mybatisplus.extension.service.IService+)")
public void dataSourcePointcut() {}
@Before("dataSourcePointcut()")
public void doBefore(JoinPoint joinPoint) {
String org = ThreadLocalContext.getOrg();
String master = "master";
if (StringUtils.isEmpty(org) || "null".equals(org) || NumberConstant.STRING_ZERO.equals(org) || master.equals(org)) {
String peek = DynamicDataSourceContextHolder.peek();
if (master.equals(peek)) {
return;
}
DynamicDataSourceContextHolder.push(master);
} else {
Set<String> set = sourceService.getList();
if (!set.contains(org)) {
throw new BusinessException("当前机构未配置数据源,请联系管理员!");
}
try {
DynamicDataSourceContextHolder.push(org);
} catch (Exception e) {
throw new BusinessException("当前机构未配置数据源,请联系管理员!");
}
}
Class<?> clazz = joinPoint.getTarget().getClass();
String methodName = joinPoint.getSignature().getName();
log.info(clazz + "类-" + methodName + "方法-" + org + "数据源");
}
@AfterReturning("dataSourcePointcut()")
public void doAfter(JoinPoint joinPoint) {
DynamicDataSourceContextHolder.poll();
}
}
自定义的当前线程请求上线文ThreadLocalContext
public class ThreadLocalContext {
private static ThreadLocal<String> threadLocalOrg = new ThreadLocal<String>();
public static String getOrg() {
return threadLocalOrg.get();
}
public static void setOrg(String org) {
threadLocalOrg.set(org);
}
public static void remove() {
threadLocalOrg.remove();
}
}
在请求拦截器里面添加线程请求的机构
@Component
public class ManageInterceptorHandler extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// ...
ThreadLocalContext.setOrg(authToken.getOrgId());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
// ...
}
}
八、总结
通过上述步骤,我们已经成功地实现了Mybatis-Plus配合Spring Boot的动态多数据源切换。这种配置方式既灵活又强大,非常适合需要处理多个数据库的现代应用程序。