一、Maven引入
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>${version}</version></dependency>
二、配置数据源
spring:datasource:dynamic:primary: master #设置默认的数据源或者数据源组,默认值即为masterdatasource:master:username: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://xx.xx.xx.xx:3306/dynamicslave_1:username: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://xx.xx.xx.xx:3307/dynamicslave_2:username: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://xx.xx.xx.xx:3308/dynamic#......省略#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
三、使用@DS切换数据
@DS可以注解在方法上和类上,同时存在方法注解优于类上注解。
注解可在service实现或mapper接口方法上,但强烈不建议同时在service和mapper注解。
| 注解 | 结果 |
|---|---|
| 没有@DS | 默认数据源 |
| @DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
# 1、 注解在service上@Service@DS("slave")public class UserServiceImpl implements UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;public List<Map<String, Object>> selectAll() {return jdbcTemplate.queryForList("select * from user");}@Override@DS("slave_1")public List<Map<String, Object>> selectByCondition() {return jdbcTemplate.queryForList("select * from user where age >10");}
2、注解在mapper接口上@DS("slave")public interface UserMapper {@Insert("INSERT INTO user (name,age) values (#{name},#{age})")boolean addUser(@Param("name") String name, @Param("age") Integer age);@Update("UPDATE user set name=#{name}, age=#{age} where id =#{id}")boolean updateUser(@Param("id") Integer id, @Param("name") String name, @Param("age") Integer age);@Delete("DELETE from user where id =#{id}")boolean deleteUser(@Param("id") Integer id);@Select("SELECT * FROM user")@DS("slave_1")List<User> selectAll();}
四、集成Druid
1、Maven引入druid-spring-boot-starter依赖
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency>
2、排除原生Druid的快速配置类
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
某些springBoot的版本上面可能无法排除(尝试使用以下方式排除)
spring:autoconfigure:exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
为什么要排除DruidDataSourceAutoConfigure?
DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,其会在原生的spring.datasource下找url,username,password等。而我们动态数据源的配置路径是变化的。
五、动态参数解析数据源(spel)
默认处理动态参数解析器 header->session->spel,所有以#开头的参数都会从参数中获取数据源。
request.getSession.setAttribute("tenantName", "slave");@DS("#session.tenantName")public List selectSPelBySession(){return userMapper.selectUsers();}@DS("#header.tenantName")//从header获取public List selectSpelByHeader() {return userMapper.selectUsers();}@DS("#tenantName")//使用spel从参数获取public List selectSpelByKey(String tenantName) {return userMapper.selectUsers();}@DS("#user.tenantName")//使用spel从复杂参数获取public List selecSpelByTenant(User user) {return userMapper.selectUsers();}
扩展
如何扩展?
- 我想从cookie中获取参数解析?
- 我想从其他环境属性中来计算?
可参考header解析器,继承DsProcessor,如果matches返回true则匹配成功,调用doDetermineDatasource返回匹配到的数据源,否则跳到写一个解析器。
public class DsHeaderProcessor extends DsProcessor {private static final String HEADER_PREFIX = "#header";@Overridepublic boolean matches(String key) {return key.startsWith(HEADER_PREFIX);}@Overridepublic String doDetermineDatasource(MethodInvocation invocation, String key) {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();return request.getHeader(key.substring(8));}}
重写完之后重新注入一个根据自己解析顺序的解析处理器。
@Bean@ConditionalOnMissingBeanpublic DsProcessor dsProcessor() {DsHeaderProcessor headerProcessor = new DsHeaderProcessor();DsSessionProcessor sessionProcessor = new DsSessionProcessor();DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();headerProcessor.setNextProcessor(sessionProcessor);sessionProcessor.setNextProcessor(spelExpressionProcessor);return headerProcessor;}
六、自定义数据源来源
mybatis-plus数据源的来源默认实现是YmlDynamicDataSourceProvider,其从yaml或properties中读取信息并解析出所有数据源信息。
接口:
public interface DynamicDataSourceProvider {/*** 加载所有数据源** @return 所有数据源,key为数据源名称*/Map<String, DataSource> loadDataSources();}
接口实现:
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.jdbc.support.JdbcUtils;import javax.sql.DataSource;import java.sql.Connection;import java.sql.DriverManager;import java.sql.Statement;import java.util.Map;@Slf4jpublic abstract class CustomizeDataSourceProvider extends AbstractDataSourceProvider implements DynamicDataSourceProvider {private String driverClassName;private String url;private String username;private String password;public CustomizeDataSourceProvider(String driverClassName, String url, String username, String password) {this.driverClassName = driverClassName;this.url = url;this.username = username;this.password = password;}/** 加载所有数据源 **/@Overridepublic Map<String, DataSource> loadDataSources(){Connection conn = null;Statement stmt = null;try {Class.forName(driverClassName);log.info("成功加载数据库驱动程序");conn = DriverManager.getConnection(url, username, password);log.info("成功获取数据库连接");stmt = conn.createStatement();Map<String, DataSourceProperty> dataSourcePropertiesMap = executeStmt(stmt);return createDataSourceMap(dataSourcePropertiesMap); // 创建数据源} catch (Exception e){e.printStackTrace();} finally {JdbcUtils.closeConnection(conn);JdbcUtils.closeStatement(stmt);}return null;}protected abstract Map<String, DataSourceProperty> executeStmt(Statement stmt) throws Exception;}
# 数据源四要素获取import lombok.Setter;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Setter@Component@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")public class MasterDatasourceProperties {protected String driverClassName;protected String url;protected String username;protected String password;}
# 获取Master数据库下 数据源表(ybg_sys_datasource) 下的数据源信息import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.HashMap;import java.util.Map;@Configurationpublic class DataSourceConfig {@Autowiredprivate MasterDatasourceProperties master;@Beanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {return new CustomizeDataSourceProvider(master.driverClassName, master.url, master.username, master.password) {@Overrideprotected Map<String, DataSourceProperty> executeStmt(Statement statement)throws SQLException {Map<String, DataSourceProperty> map = new HashMap<>();//*************** ↑↑↑↑↑↑↑ here is needn't in prod env***************ResultSet rs = statement.executeQuery("select * from ybg_sys_datasource");while (rs.next()) {String name = rs.getString("name");String username = rs.getString("username");String password = rs.getString("password");String url = rs.getString("url");String driver = rs.getString("driver_class_name");DataSourceProperty property = new DataSourceProperty();property.setUsername(username);property.setPassword(password);property.setUrl(url);property.setDriverClassName(driver);map.put(name, property);}return map;}};}}
