动态数据源
文档链接

一、Maven引入

  1. <dependency>
  2. <groupId>com.baomidou</groupId>
  3. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  4. <version>${version}</version>
  5. </dependency>

版本大于2.3.4可使用动态切换数据源

二、配置数据源

  1. spring:
  2. datasource:
  3. dynamic:
  4. primary: master #设置默认的数据源或者数据源组,默认值即为master
  5. datasource:
  6. master:
  7. username: root
  8. password: 123456
  9. driver-class-name: com.mysql.jdbc.Driver
  10. url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
  11. slave_1:
  12. username: root
  13. password: 123456
  14. driver-class-name: com.mysql.jdbc.Driver
  15. url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
  16. slave_2:
  17. username: root
  18. password: 123456
  19. driver-class-name: com.mysql.jdbc.Driver
  20. url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
  21. #......省略
  22. #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

三、使用@DS切换数据

@DS可以注解在方法上和类上,同时存在方法注解优于类上注解。
注解可在service实现或mapper接口方法上,但强烈不建议同时在service和mapper注解。

注解 结果
没有@DS 默认数据源
@DS(“dsName”) dsName可以为组名也可以为具体某个库的名称
  1. # 1、 注解在service上
  2. @Service
  3. @DS("slave")
  4. public class UserServiceImpl implements UserService {
  5. @Autowired
  6. private JdbcTemplate jdbcTemplate;
  7. public List<Map<String, Object>> selectAll() {
  8. return jdbcTemplate.queryForList("select * from user");
  9. }
  10. @Override
  11. @DS("slave_1")
  12. public List<Map<String, Object>> selectByCondition() {
  13. return jdbcTemplate.queryForList("select * from user where age >10");
  14. }
  1. 2、注解在mapper接口上
  2. @DS("slave")
  3. public interface UserMapper {
  4. @Insert("INSERT INTO user (name,age) values (#{name},#{age})")
  5. boolean addUser(@Param("name") String name, @Param("age") Integer age);
  6. @Update("UPDATE user set name=#{name}, age=#{age} where id =#{id}")
  7. boolean updateUser(@Param("id") Integer id, @Param("name") String name, @Param("age") Integer age);
  8. @Delete("DELETE from user where id =#{id}")
  9. boolean deleteUser(@Param("id") Integer id);
  10. @Select("SELECT * FROM user")
  11. @DS("slave_1")
  12. List<User> selectAll();
  13. }

四、集成Druid

1、Maven引入druid-spring-boot-starter依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>druid-spring-boot-starter</artifactId>
  4. <version>1.1.10</version>
  5. </dependency>

2、排除原生Druid的快速配置类

  1. @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
  2. public class Application {
  3. public static void main(String[] args) {
  4. SpringApplication.run(Application.class, args);
  5. }
  6. }

某些springBoot的版本上面可能无法排除(尝试使用以下方式排除)

  1. spring:
  2. autoconfigure:
  3. exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure

为什么要排除DruidDataSourceAutoConfigure?

DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,其会在原生的spring.datasource下找url,username,password等。而我们动态数据源的配置路径是变化的。

五、动态参数解析数据源(spel)

默认处理动态参数解析器 header->session->spel,所有以#开头的参数都会从参数中获取数据源。

  1. request.getSession.setAttribute("tenantName", "slave");
  2. @DS("#session.tenantName")
  3. public List selectSPelBySession(){
  4. return userMapper.selectUsers();
  5. }
  6. @DS("#header.tenantName")//从header获取
  7. public List selectSpelByHeader() {
  8. return userMapper.selectUsers();
  9. }
  10. @DS("#tenantName")//使用spel从参数获取
  11. public List selectSpelByKey(String tenantName) {
  12. return userMapper.selectUsers();
  13. }
  14. @DS("#user.tenantName")//使用spel从复杂参数获取
  15. public List selecSpelByTenant(User user) {
  16. return userMapper.selectUsers();
  17. }

扩展

如何扩展?

  1. 我想从cookie中获取参数解析?
  2. 我想从其他环境属性中来计算?

可参考header解析器,继承DsProcessor,如果matches返回true则匹配成功,调用doDetermineDatasource返回匹配到的数据源,否则跳到写一个解析器。

  1. public class DsHeaderProcessor extends DsProcessor {
  2. private static final String HEADER_PREFIX = "#header";
  3. @Override
  4. public boolean matches(String key) {
  5. return key.startsWith(HEADER_PREFIX);
  6. }
  7. @Override
  8. public String doDetermineDatasource(MethodInvocation invocation, String key) {
  9. HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  10. return request.getHeader(key.substring(8));
  11. }
  12. }

重写完之后重新注入一个根据自己解析顺序的解析处理器。

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public DsProcessor dsProcessor() {
  4. DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
  5. DsSessionProcessor sessionProcessor = new DsSessionProcessor();
  6. DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
  7. headerProcessor.setNextProcessor(sessionProcessor);
  8. sessionProcessor.setNextProcessor(spelExpressionProcessor);
  9. return headerProcessor;
  10. }

六、自定义数据源来源

mybatis-plus数据源的来源默认实现是YmlDynamicDataSourceProvider,其从yaml或properties中读取信息并解析出所有数据源信息。
接口:

  1. public interface DynamicDataSourceProvider {
  2. /**
  3. * 加载所有数据源
  4. *
  5. * @return 所有数据源,key为数据源名称
  6. */
  7. Map<String, DataSource> loadDataSources();
  8. }

接口实现:

  1. import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
  2. import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
  3. import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.boot.context.properties.ConfigurationProperties;
  6. import org.springframework.jdbc.support.JdbcUtils;
  7. import javax.sql.DataSource;
  8. import java.sql.Connection;
  9. import java.sql.DriverManager;
  10. import java.sql.Statement;
  11. import java.util.Map;
  12. @Slf4j
  13. public abstract class CustomizeDataSourceProvider extends AbstractDataSourceProvider implements DynamicDataSourceProvider {
  14. private String driverClassName;
  15. private String url;
  16. private String username;
  17. private String password;
  18. public CustomizeDataSourceProvider(String driverClassName, String url, String username, String password) {
  19. this.driverClassName = driverClassName;
  20. this.url = url;
  21. this.username = username;
  22. this.password = password;
  23. }
  24. /** 加载所有数据源 **/
  25. @Override
  26. public Map<String, DataSource> loadDataSources(){
  27. Connection conn = null;
  28. Statement stmt = null;
  29. try {
  30. Class.forName(driverClassName);
  31. log.info("成功加载数据库驱动程序");
  32. conn = DriverManager.getConnection(url, username, password);
  33. log.info("成功获取数据库连接");
  34. stmt = conn.createStatement();
  35. Map<String, DataSourceProperty> dataSourcePropertiesMap = executeStmt(stmt);
  36. return createDataSourceMap(dataSourcePropertiesMap); // 创建数据源
  37. } catch (Exception e){
  38. e.printStackTrace();
  39. } finally {
  40. JdbcUtils.closeConnection(conn);
  41. JdbcUtils.closeStatement(stmt);
  42. }
  43. return null;
  44. }
  45. protected abstract Map<String, DataSourceProperty> executeStmt(Statement stmt) throws Exception;
  46. }
  1. # 数据源四要素获取
  2. import lombok.Setter;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.stereotype.Component;
  5. @Setter
  6. @Component
  7. @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
  8. public class MasterDatasourceProperties {
  9. protected String driverClassName;
  10. protected String url;
  11. protected String username;
  12. protected String password;
  13. }
  1. # 获取Master数据库下 数据源表(ybg_sys_datasource) 下的数据源信息
  2. import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
  3. import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9. import java.sql.Statement;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. @Configuration
  13. public class DataSourceConfig {
  14. @Autowired
  15. private MasterDatasourceProperties master;
  16. @Bean
  17. public DynamicDataSourceProvider dynamicDataSourceProvider() {
  18. return new CustomizeDataSourceProvider(master.driverClassName, master.url, master.username, master.password) {
  19. @Override
  20. protected Map<String, DataSourceProperty> executeStmt(Statement statement)
  21. throws SQLException {
  22. Map<String, DataSourceProperty> map = new HashMap<>();
  23. //*************** ↑↑↑↑↑↑↑ here is needn't in prod env***************
  24. ResultSet rs = statement.executeQuery("select * from ybg_sys_datasource");
  25. while (rs.next()) {
  26. String name = rs.getString("name");
  27. String username = rs.getString("username");
  28. String password = rs.getString("password");
  29. String url = rs.getString("url");
  30. String driver = rs.getString("driver_class_name");
  31. DataSourceProperty property = new DataSourceProperty();
  32. property.setUsername(username);
  33. property.setPassword(password);
  34. property.setUrl(url);
  35. property.setDriverClassName(driver);
  36. map.put(name, property);
  37. }
  38. return map;
  39. }
  40. };
  41. }
  42. }