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);
}