基于Mybatis-Plus和dynamic-datasource-spring-boot-starter-多租户多商户多数据源动态切换

9 篇文章 0 订阅
4 篇文章 0 订阅

 

基础知识

1、引入dynamic-datasource-spring-boot-starter。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

2、配置默认数据源

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置

3、使用 @DS切换数据源。

@DS可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

注解结果
没有@DS默认数据源
@DS("dsName")dsName可以为组名也可以为具体某个库的名称
@Service
@DS("master")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("master")
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

动态切换数据源方案

基于dynamic-datasource-spring-boot-starter实现

前置条件:加载数据源从数据库中查询

@Data
@Component
@ConfigurationProperties("spring.datasource")
public class DataSourceProperties {

	/**
	 * 用户名
	 */
	private String username;

	/**
	 * 密码
	 */
	private String password;

	/**
	 * jdbcurl
	 */
	private String url;

	/**
	 * 驱动类型
	 */
	private String driverClassName;

	/**
	 * 查询数据源的SQL
	 */
	private String queryDsSql = "select * from gen_datasource_conf where del_flag = 0";

}
public class JdbcDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider {

	private final DataSourceProperties properties;

	private final StringEncryptor stringEncryptor;

	public JdbcDynamicDataSourceProvider(StringEncryptor stringEncryptor, DataSourceProperties properties) {
		super(properties.getDriverClassName(), properties.getUrl(), properties.getUsername(), properties.getPassword());
		this.stringEncryptor = stringEncryptor;
		this.properties = properties;
	}

	/**
	 * 执行语句获得数据源参数
	 * @param statement 语句
	 * @return 数据源参数
	 * @throws SQLException sql异常
	 */
	@Override
	protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
		ResultSet rs = statement.executeQuery(properties.getQueryDsSql());

		Map<String, DataSourceProperty> map = new HashMap<>(8);
		while (rs.next()) {
			String name = rs.getString(DataSourceConstants.DS_NAME);
			String username = rs.getString(DataSourceConstants.DS_USER_NAME);
			String password = rs.getString(DataSourceConstants.DS_USER_PWD);
			String url = rs.getString(DataSourceConstants.DS_JDBC_URL);
			DataSourceProperty property = new DataSourceProperty();
			property.setDriverClassName(DataSourceConstants.DS_DRIVER);
			property.setUsername(username);
			property.setLazy(true);
			property.setPassword(stringEncryptor.decrypt(password));
			property.setUrl(url);
			map.put(name, property);
		}

		// 添加默认主数据源
		DataSourceProperty property = new DataSourceProperty();
		property.setUsername(properties.getUsername());
		property.setPassword(properties.getPassword());
		property.setUrl(properties.getUrl());
		property.setLazy(true);
		property.setDriverClassName(DataSourceConstants.DS_DRIVER);
		map.put(DataSourceConstants.DS_MASTER, property);
		return map;
	}

}

 

切换方案:

1、@DS-使用header切换数据源

//todo dynamic-datasource-spring-boot-starter源码
public class DsHeaderProcessor extends DsProcessor {

    /**
     * header prefix
     */
    private static final String HEADER_PREFIX = "#header";

    @Override
    public boolean matches(String key) {
        return key.startsWith(HEADER_PREFIX);
    }

    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getHeader(key.substring(8));
    }
}
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DynamicDataSourceAutoConfiguration {
    @Bean
	public DsProcessor dsProcessor() {
		return new DsHeaderProcessor();
	}
}


    /**
     * 分页查询表
     *
     * @param tableName 查询条件
     * @param dsName
     * @return
     */
    @Override
    @DS("#header")
    public IPage<List<Map<String, Object>>> getPage(Page page, String tableName, String dsName) {
        return generatorMapper.queryList(page, tableName);
    }

 

2、@DS-使用session切换数据源

//todo dynamic-datasource-spring-boot-starter源码
public class DsSessionProcessor extends DsProcessor {

    /**
     * session开头
     */
    private static final String SESSION_PREFIX = "#session";

    @Override
    public boolean matches(String key) {
        return key.startsWith(SESSION_PREFIX);
    }

    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getSession().getAttribute(key.substring(9)).toString();
    }
}

 

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DynamicDataSourceAutoConfiguration {
    @Bean
	public DsProcessor dsProcessor() {
		return new DsSessionProcessor();
	}
}


    /**
     * 分页查询表
     *
     * @param tableName 查询条件
     * @param dsName
     * @return
     */
    @Override
    @DS("#session")
    public IPage<List<Map<String, Object>>> getPage(Page page, String tableName, String dsName) {
        return generatorMapper.queryList(page, tableName);
    }

 

3、@DS-使用Spel表达式切换数据源

//todo dynamic-datasource-spring-boot-starter源码
public class DsSpelExpressionProcessor extends DsProcessor {

    /**
     * 参数发现器
     */
    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    /**
     * Express语法解析器
     */
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    /**
     * 解析上下文的模板
     * 对于默认不设置的情况下,从参数中取值的方式 #param1
     * 设置指定模板 ParserContext.TEMPLATE_EXPRESSION 后的取值方式: #{#param1}
     * issues: https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/199
     */
    private ParserContext parserContext = new ParserContext() {

        @Override
        public boolean isTemplate() {
            return false;
        }

        @Override
        public String getExpressionPrefix() {
            return null;
        }

        @Override
        public String getExpressionSuffix() {
            return null;
        }
    };

    @Override
    public boolean matches(String key) {
        return true;
    }

    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        Method method = invocation.getMethod();
        Object[] arguments = invocation.getArguments();
        EvaluationContext context = new MethodBasedEvaluationContext(null, method, arguments, NAME_DISCOVERER);
        final Object value = PARSER.parseExpression(key, parserContext).getValue(context);
        return value == null ? null : value.toString();
    }

    public void setParserContext(ParserContext parserContext) {
        this.parserContext = parserContext;
    }
}

 

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DynamicDataSourceAutoConfiguration {
    @Bean
	public DsProcessor dsProcessor() {
		return new DsSpelExpressionProcessor();
	}
}


    /**
     * 分页查询表
     *
     * @param tableName 查询条件
     * @param dsName
     * @return
     */
    @Override
    @DS("#{dsName}")
    public IPage<List<Map<String, Object>>> getPage(Page page, String tableName, String dsName) {
        return generatorMapper.queryList(page, tableName);
    }

 

 

4、扩展@DS-取最后一个参数切换数据源

/**
 * 参数数据源解析 @DS("#last)
 */
public class LastParamDsProcessor extends DsProcessor {

	private static final String LAST_PREFIX = "#last";

	/**
	 * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
	 * @param key DS注解里的内容
	 * @return 是否匹配
	 */
	@Override
	public boolean matches(String key) {
		if (key.startsWith(LAST_PREFIX)) {
			DynamicDataSourceContextHolder.clear();
			return true;
		}
		return false;
	}

	/**
	 * 抽象最终决定数据源
	 * @param invocation 方法执行信息
	 * @param key DS注解里的内容
	 * @return 数据源名称
	 */
	@Override
	public String doDetermineDatasource(MethodInvocation invocation, String key) {
		Object[] arguments = invocation.getArguments();
		return String.valueOf(arguments[arguments.length - 1]);
	}

}



@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DynamicDataSourceAutoConfiguration {
    @Bean
	public DsProcessor dsProcessor() {
		return new LastParamDsProcessor();
	}
}

 

	/**
	 * 分页查询表
	 * @param tableName 查询条件
	 * @param dsName
	 * @return
	 */
	@Override
	@DS("#last")
	public IPage<List<Map<String, Object>>> getPage(Page page, String tableName, String dsName) {
		return generatorMapper.queryList(page, tableName);
	}
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
mybatis-plus-dynamic-datasource 中获取多数据源实例的方法如下: 1. 导入相关依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>2.5.3</version> </dependency> ``` 2. 在 application.yml 或 application.properties 中配置数据源信息,例如: ```yaml spring: datasource: dynamic: primary: ds1 # 设置默认数据源 datasource: ds1: url: jdbc:mysql://localhost:3306/db1 username: root password: 123456 ds2: url: jdbc:mysql://localhost:3306/db2 username: root password: 123456 ``` 3. 创建一个继承自 AbstractRoutingDataSource 的类,用于动态切换数据源: ```java public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } } ``` 4. 创建一个数据源上下文持有者类,用于设置和获取当前线程使用的数据源: ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSourceKey(String dataSourceKey) { CONTEXT_HOLDER.set(dataSourceKey); } public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } } ``` 5. 配置数据源切换策略和数据源 bean: ```java @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.dynamic") public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean public DataSource dataSource(DataSourceProperties properties) { Map<String, DataSource> dataSourceMap = new HashMap<>(); dataSourceMap.put("ds1", properties.getDataSource("ds1")); dataSourceMap.put("ds2", properties.getDataSource("ds2")); // 设置默认数据源 DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(); dataSource.setDefaultTargetDataSource(dataSourceMap.get(properties.getPrimary())); dataSource.setTargetDataSources(dataSourceMap); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); return sessionFactoryBean.getObject(); } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } ``` 现在,你可以通过注入 DataSource 类型的 bean 来获取多数据源实例,例如: ```java @Autowired private DataSource dataSource; ``` 然后,你就可以使用这个数据源实例来执行数据库操作了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值