基于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自定义数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!