一、Maven引入
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
二、配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
datasource:
master:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
slave_1:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
slave_2:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: 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 {
@Autowired
private 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";
@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));
}
}
重写完之后重新注入一个根据自己解析顺序的解析处理器。
@Bean
@ConditionalOnMissingBean
public 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;
@Slf4j
public 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;
}
/** 加载所有数据源 **/
@Override
public 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;
@Configuration
public class DataSourceConfig {
@Autowired
private MasterDatasourceProperties master;
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
return new CustomizeDataSourceProvider(master.driverClassName, master.url, master.username, master.password) {
@Override
protected 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;
}
};
}
}