基於mybatis plus實現數據源動態添加、刪除、切換,自定義數據源的示例代碼
簡介
基於springboot,mybatis plus集成瞭一套多數據源的解決方案,在使用時引入相應的插件dynamic-datasource-spring-boot-starter,可以實現數據源的動態添加、刪除等功能,對於多租戶或者分庫等操作可以根據AOP切面代理到不同的數據源、實現單一系統數據隔離的目的。
代碼示例
mavne依賴
<!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.4</version> </dependency> <!--dynamic-datasource--> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.4.1</version> </dependency>
數據源增加、移除
@RestController @RequestMapping("/datasources") public class DataSourceController { @Resource private DataSource dataSource; @Resource private DefaultDataSourceCreator dataSourceCreator; @GetMapping("list") public Set<String> list() { DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; return ds.getDataSources().keySet(); } @PostMapping("add") public Set<String> add(@Validated @RequestBody DataSourceDTO dto) { DataSourceProperty dataSourceProperty = new DataSourceProperty(); BeanUtils.copyProperties(dto, dataSourceProperty); DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty); ds.addDataSource(dto.getPollName(), dataSource); return ds.getDataSources().keySet(); } @DeleteMapping("remove") public void remove(String name) { DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; ds.removeDataSource(name); } }
默認的數據源連接池加載順序為: druid>hikaricp>beecp>dbcp>spring basic
數據源切換
基於AOP切換
添加註解,排除不做切換的接口
package com.starsray.dynamic.datasource.annotation; import java.lang.annotation.*; /** * <p> * 用戶標識僅可以使用默認數據源 * </p> * * @author starsray * @since 2021-11-10 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DefaultDs { }
切面具體實現
package com.starsray.dynamic.datasource.interceptor; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import com.starsray.dynamic.datasource.annotation.DefaultDs; import com.starsray.dynamic.datasource.exception.ExceptionEnum; import com.starsray.dynamic.datasource.exception.GlobalException; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; /** * <p> * 數據源選擇器切面 * </p> * * @author starsray * @since 2021-11-10 */ @Aspect @Component @RequiredArgsConstructor(onConstructor_ = @Autowired) public class DsInterceptor implements HandlerInterceptor { @Pointcut("execution(public * com.starsray.dynamic.datasource.controller.*.*(..))") public void datasourcePointcut() { } /** * 前置操作,攔截具體請求,獲取header裡的數據源id,設置線程變量裡,用於後續切換數據源 */ @Before("datasourcePointcut()") public void doBefore(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); // 排除不可切換數據源的方法 DefaultDs annotation = method.getAnnotation(DefaultDs.class); if (null != annotation) { DynamicDataSourceContextHolder.push("master"); } else { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; assert attributes != null; HttpServletRequest request = attributes.getRequest(); String header = request.getHeader("tenantName"); if (StringUtils.isNotBlank(header)) { DynamicDataSourceContextHolder.push(header); } else { throw new GlobalException(ExceptionEnum.NOT_TENANT); } } } /** * 後置操作,設置回默認的數據源id */ @AfterReturning("datasourcePointcut()") public void doAfter() { DynamicDataSourceContextHolder.push("master"); } }
基於重寫處理器
mybatis plus提供瞭默認處理器來決定使用的數據源,可以重寫處理器實現自定義參數,比如從請求header裡面獲取參數切換數據源。
@DS("#header.tenantId")
自定義處理器
public class HeaderProcessor extends DsProcessor { private static final String HEADER = "#header"; @Override public boolean matches(String key) { return key.startsWith(HEADER); } @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request.getHeader(key.substring(8)); } }
註冊自定義處理器
@Configuration public class CustomerDynamicDataSourceConfig{ @Bean public DsProcessor dsProcessor() { DsHeaderProcessor headerProcessor = new DsHeaderProcessor(); DsSessionProcessor sessionProcessor = new DsSessionProcessor(); DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor(); headerProcessor.setNextProcessor(sessionProcessor); sessionProcessor.setNextProcessor(spelExpressionProcessor); return headerProcessor; } }
如果有場景需要手動切換數據源,可以使用組件提供的工具來實現。
DynamicDataSourceContextHolder.push("master");
自定義數據源
mybatis plus提供瞭一個接口來加載數據源信息。
public interface DynamicDataSourceProvider { Map<String, DataSource> loadDataSources(); }
這個接口有一個抽象實現類AbstractDataSourceProvider,通過模板方法定義瞭加載數據源來源的方式,mybatis plus通過YmlDynamicDataSourceProvider實現瞭讀取yml文件配置來初始化數據源的方式。
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider { private static final Logger log = LoggerFactory.getLogger(AbstractDataSourceProvider.class); @Autowired private DefaultDataSourceCreator defaultDataSourceCreator; public AbstractDataSourceProvider() { } protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) { Map<String, DataSource> dataSourceMap = new HashMap(dataSourcePropertiesMap.size() * 2); Iterator var3 = dataSourcePropertiesMap.entrySet().iterator(); while(var3.hasNext()) { Entry<String, DataSourceProperty> item = (Entry)var3.next(); String dsName = (String)item.getKey(); DataSourceProperty dataSourceProperty = (DataSourceProperty)item.getValue(); String poolName = dataSourceProperty.getPoolName(); if (poolName == null || "".equals(poolName)) { poolName = dsName; } dataSourceProperty.setPoolName(poolName); dataSourceMap.put(dsName, this.defaultDataSourceCreator.createDataSource(dataSourceProperty)); } return dataSourceMap; } }
如果有需要從數據庫加載數據源信息,可以重寫AbstractJdbcDataSourceProvider中的executeStmt方法來加載數據庫配置信息。示例:
package com.digital.cnzz.dynamic.ds.provider; import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider; import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty; import com.digital.cnzz.dynamic.ds.config.DefaultDsConfig; import com.digital.cnzz.dynamic.ds.constant.DsDriverEnum; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.annotation.Resource; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; @Primary @Configuration public class DsProvider { @Resource private DefaultDsConfig defaultDsConfig; @Bean public DynamicDataSourceProvider jdbcDynamicDataSourceProvider() { return new AbstractJdbcDataSourceProvider(defaultDsConfig.getDriverClassName(), defaultDsConfig.getUrl(), defaultDsConfig.getUsername(), defaultDsConfig.getPassword()) { @Override protected Map<String, DataSourceProperty> executeStmt(Statement statement) { Map<String, DataSourceProperty> dataSourcePropertiesMap = null; ResultSet rs = null; try { dataSourcePropertiesMap = new HashMap<>(); rs = statement.executeQuery("SELECT * FROM DYNAMIC_DATASOURCE_INSTANCE"); while (rs.next()) { String name = rs.getString("name"); DataSourceProperty property = new DataSourceProperty(); property.setDriverClassName(rs.getString("driver")); property.setUrl(rs.getString("url")); property.setUsername(rs.getString("username")); property.setPassword(rs.getString("password")); dataSourcePropertiesMap.put(name, property); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if (rs != null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); } try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } return dataSourcePropertiesMap; } }; } }
通過讀取源碼可以發現,如果還有其他需要自定義加載數據源的方式,隻需要繼承AbstractDataSourceProvider抽象類,實現DynamicDataSourceProvider接口,重寫loadDataSources方法就可以實現自定義數據源。
完整代碼示例:https://gitee.com/starsray/dynamic-datasource
到此這篇關於基於mybatis plus實現數據源動態添加、刪除、切換,自定義數據源的文章就介紹到這瞭,更多相關mybatis plus自定義數據源內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 解決mybatis-plus動態數據源切換不生效的問題
- Spring基礎之AOP的概念介紹
- Spring AOP實現記錄操作日志
- SpringMVC記錄我遇到的坑_AOP註解無效,切面不執行的解決
- SpringBoot使用Aspect切面攔截打印請求參數的示例代碼