基于mybatis plus实现数据源动态添加、删除、切换,自定义数据源的示例代码

网友投稿 1617 2022-10-22

基于mybatis plus实现数据源动态添加、删除、切换,自定义数据源的示例代码

基于mybatis plus实现数据源动态添加、删除、切换,自定义数据源的示例代码

目录简介代码示例mavne依赖数据源增加、移除数据源切换基于AOP切换基于重写处理器自定义数据源

简介

基于springboot,mybatis plus集成了一套多数据源的解决方案,在使用时引入相应的插件dynamic-datasource-spring-boot-starter,可以实现数据源的动态添加、删除等功能,对于多租户或者分库等操作可以根据AOP切面代理到不同的数据源、实现单一系统数据隔离的目的。

代码示例

mavne依赖

com.baomidou

mybatis-plus-boot-starter

3.4.3.4

com.baomidou

dynamic-datasource-spring-boot-starter

3.4.1

数据源增加、移除

@RestController

@RequestMapping("/datasources")

public class DataSourceController {

@Resource

private DataSource dataSource;

@Resource

private DefaultDataSourceCreator dataSourceCreator;

@GetMapping("list")

public Set list() {

DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;

return ds.getDataSources().keySet();

}

@PostMapping("add")

public Set 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.*;

/**

*

* 用户标识仅可以使用默认数据源

*

*

* @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;

/**

*

* 数据源选择器切面

*

*

* @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 iTxBgjpTUICnterface DynamicDataSourceProvider {

Map 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 createDataSourceMap(Map dataSourcePropertiesMap) {

Map dataSourceMap = new HashMap(dataSourcePropertiesMap.size() * 2);

Iterator var3 = dataSourcePropertiesMap.entrySet().iterator();

while(var3.hasNext()) {

Entry 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-zz.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-zz.dynamic.ds.config.DefaultDsConfig;

import com.digital-zz.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 executeStmt(Statement statement) {

Map 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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:luogu2296 寻找道路
下一篇:小邵教你玩转Typescript、ts版React全家桶脚手架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~