<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
基於springboot,mybatis plus整合了一套多資料來源的解決方案,在使用時引入相應的外掛dynamic-datasource-spring-boot-starter,可以實現資料來源的動態新增、刪除等功能,對於多租戶或者分庫等操作可以根據AOP切面代理到不同的資料來源、實現單一系統資料隔離的目的。
<!--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
新增註解,排除不做切換的介面
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自定義資料來源內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45