基于mybatis?plus實現(xiàn)數(shù)據(jù)源動態(tài)添加、刪除、切換,自定義數(shù)據(jù)源的示例代碼
簡介
基于springboot,mybatis plus集成了一套多數(shù)據(jù)源的解決方案,在使用時引入相應(yīng)的插件dynamic-datasource-spring-boot-starter,可以實現(xiàn)數(shù)據(jù)源的動態(tài)添加、刪除等功能,對于多租戶或者分庫等操作可以根據(jù)AOP切面代理到不同的數(shù)據(jù)源、實現(xiàn)單一系統(tǒng)數(shù)據(jù)隔離的目的。
代碼示例
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>
數(shù)據(jù)源增加、移除
@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); } }
默認的數(shù)據(jù)源連接池加載順序為: druid>hikaricp>beecp>dbcp>spring basic
數(shù)據(jù)源切換
基于AOP切換
添加注解,排除不做切換的接口
package com.starsray.dynamic.datasource.annotation; import java.lang.annotation.*; /** * <p> * 用戶標識僅可以使用默認數(shù)據(jù)源 * </p> * * @author starsray * @since 2021-11-10 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DefaultDs { }
切面具體實現(xiàn)
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> * 數(shù)據(jù)源選擇器切面 * </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里的數(shù)據(jù)源id,設(shè)置線程變量里,用于后續(xù)切換數(shù)據(jù)源 */ @Before("datasourcePointcut()") public void doBefore(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); // 排除不可切換數(shù)據(jù)源的方法 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); } } } /** * 后置操作,設(shè)置回默認的數(shù)據(jù)源id */ @AfterReturning("datasourcePointcut()") public void doAfter() { DynamicDataSourceContextHolder.push("master"); } }
基于重寫處理器
mybatis plus提供了默認處理器來決定使用的數(shù)據(jù)源,可以重寫處理器實現(xiàn)自定義參數(shù),比如從請求header里面獲取參數(shù)切換數(shù)據(jù)源。
@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; } }
如果有場景需要手動切換數(shù)據(jù)源,可以使用組件提供的工具來實現(xiàn)。
DynamicDataSourceContextHolder.push("master");
自定義數(shù)據(jù)源
mybatis plus提供了一個接口來加載數(shù)據(jù)源信息。
public interface DynamicDataSourceProvider { Map<String, DataSource> loadDataSources(); }
這個接口有一個抽象實現(xiàn)類AbstractDataSourceProvider,通過模板方法定義了加載數(shù)據(jù)源來源的方式,mybatis plus通過YmlDynamicDataSourceProvider實現(xiàn)了讀取yml文件配置來初始化數(shù)據(jù)源的方式。
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; } }
如果有需要從數(shù)據(jù)庫加載數(shù)據(jù)源信息,可以重寫AbstractJdbcDataSourceProvider中的executeStmt方法來加載數(shù)據(jù)庫配置信息。示例:
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; } }; } }
通過讀取源碼可以發(fā)現(xiàn),如果還有其他需要自定義加載數(shù)據(jù)源的方式,只需要繼承AbstractDataSourceProvider抽象類,實現(xiàn)DynamicDataSourceProvider接口,重寫loadDataSources方法就可以實現(xiàn)自定義數(shù)據(jù)源。
完整代碼示例:https://gitee.com/starsray/dynamic-datasource
到此這篇關(guān)于基于mybatis plus實現(xiàn)數(shù)據(jù)源動態(tài)添加、刪除、切換,自定義數(shù)據(jù)源的文章就介紹到這了,更多相關(guān)mybatis plus自定義數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud-Spring?Boot?Starter使用測試及問題小結(jié)
Spring?Boot?Starter?是在?SpringBoot?組件中被提出來的一種概念、簡化了很多煩瑣的配置、通過引入各種?Spring?Boot?Starter?包可以快速搭建出一個項目的腳手架,這篇文章主要介紹了SpringCloud-Spring?Boot?Starter使用測試,需要的朋友可以參考下2022-07-07java實現(xiàn)獲取網(wǎng)站的keywords,description
這篇文章主要介紹了java實現(xiàn)獲取網(wǎng)站的keywords,description的相關(guān)資料,需要的朋友可以參考下2015-03-03springcloud微服務(wù)基于redis集群的單點登錄實現(xiàn)解析
這篇文章主要介紹了springcloud微服務(wù)基于redis集群的單點登錄實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09MyBatis-Plus Sequence主鍵的實現(xiàn)
這篇文章主要介紹了MyBatis-Plus Sequence主鍵的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12Java Web檢查用戶登錄狀態(tài)(防止用戶訪問到非法頁面)
一般javaweb網(wǎng)站都有用戶登錄,而有一些操作必須用戶登錄才能進行,本文主要介紹了Java Web檢查用戶登錄狀態(tài),具有一定的參考價值,感興趣的可以了解一下2023-09-09