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

作者:※星光※ 时间:2021-08-20 20:07:46 

简介

基于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

来源:https://blog.csdn.net/qq_38721537/article/details/121434339

标签:mybatis,plus,自定义,数据源
0
投稿

猜你喜欢

  • java IO流 之 输出流 OutputString()的使用

    2023-08-11 23:16:30
  • Spring-boot JMS 发送消息慢的解决方法

    2023-02-06 07:50:54
  • C++实现无重复字符的最长子串

    2023-11-02 22:49:00
  • 一篇文章带你入门Java接口

    2023-11-06 02:07:55
  • Java 如何优雅的抛出业务异常

    2023-11-24 04:04:34
  • 浅谈SpringMVC的执行流程

    2023-09-30 17:59:59
  • 解决SpringMVC、tomcat、Intellij idea、ajax中文乱码问题

    2023-10-20 10:16:23
  • spring hibernate实现动态替换表名(分表)的方法

    2022-06-10 14:20:03
  • Java实现几种序列化方式总结

    2023-02-13 06:18:27
  • java中Swing会奔跑的线程侠

    2021-12-14 23:47:36
  • java设计模式学习之工厂方法模式

    2023-10-12 17:19:04
  • 详解使用Spring的BeanPostProcessor优雅的实现工厂模式

    2023-01-14 02:15:53
  • 详解Java设计模式——命令模式

    2023-11-23 05:00:07
  • Java FineReport报表工具导出EXCEL的四种方式

    2021-07-20 02:58:17
  • SpringBoot如何优雅的整合Swagger Api自动生成文档

    2022-08-10 00:49:29
  • RocketMQ实现随缘分BUG小功能示例详解

    2023-01-20 04:21:05
  • java中生产者消费者问题和代码案例

    2023-11-24 04:09:07
  • Java将String字符串带括号转成List的简单方法

    2022-10-26 18:20:17
  • 线程池中execute与submit的区别说明

    2023-03-18 23:09:04
  • springboot读取文件,打成jar包后访问不到的解决

    2023-07-14 13:56:35
  • asp之家 软件编程 m.aspxhome.com