在pom.xml文件中加入以下依赖:
com.baomidou
dynamic-datasource-spring-boot-starter
3.2.0
在application.yml或application.properties中配置数据源,如:
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,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可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
其中,spring.datasource.dynamic.primary为默认数据源,后面的配置为各个数据源的配置。
在需要使用的地方使用@DS注解,指定使用的数据源,如:
//@DS("slave_1")
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@DS("slave_1")
@Override
public List listUsersBySlave1() {
return userMapper.listUsers();
}
@Override
public List listUsersByMaster() {
return userMapper.listUsers();
}
}
@DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解,如果没有写@DS注解,则默认使用master数据源。
有时候切换数据源需要根据业务动态切换,就不能使用注解,需要手动切换数据:
DynamicDataSourceContextHolder.push("slave_1");
//执行业务方法
DynamicDataSourceContextHolder.poll();
手动切换数据源会与springboot事务冲突,例如
@Transactional(rollbackFor = Exception.class)
public void add(DataSourceAddVo vo) {
DynamicDataSourceContextHolder.push("slave_1");
//执行业务方法
selectList();
handleAdd();
DynamicDataSourceContextHolder.poll();
}
上述代码中,执行handleAdd方法不能执行切换数据源,可以改造成:
//@Transactional(rollbackFor = Exception.class)
public void add(DataSourceAddVo vo) {
DynamicDataSourceContextHolder.push("slave_1");
//执行业务方法
selectList();
DynamicDataSourceContextHolder.poll();
}
@Transactional(rollbackFor = Exception.class)
public void handleAdd( ){
this.save(bean);
}
以上切换的数据源都是在配置文件上定义好了,如果需要由用户从界面添加数据源则需要动态维护数据源。
数据源由:数据源唯一编码、用户名、密码、url和驱动4个元素组成,见下图:
编写数据源管理的工具类:
package org.bitnei.modules.ds;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Set;
/**
* 功能描述:数据源管理工具类
*
* @author: zenghaiwen
* @date: 2023年07月20日 10:05
*/
@Component
@Slf4j
public class DynamicDataSourcesUtil {
@Autowired
private DataSource dataSource;
@Autowired
private DataSourceCreator dataSourceCreator;
/**
* 添加数据源
*/
public Set<String> addDataSource(DataSourceDTO dto) {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
if(!this.existsDataSource(dto.getPoolName())){
new JdbcTemplate(dataSource);
DataSourceProperty dataSourceProperty = new DataSourceProperty();
BeanUtils.copyProperties(dto, dataSourceProperty);
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
ds.addDataSource(dto.getPoolName(), dataSource);
}
return ds.getCurrentDataSources().keySet();
}
public DataSource getDataSource(){
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
return ds.getDataSource("master");
}
/**
* 删除数据源
* @param name
*/
public void removeDataSource(String name){
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.removeDataSource(name);
}
/**
* 获取当前所有数据源
* @return
*/
public Set<String> allDataSource() {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
return ds.getCurrentDataSources().keySet();
}
/**
* 是否存在数据源
* @param name
* @return
*/
public boolean existsDataSource(String name){
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
return ds.getCurrentDataSources().containsKey(name);
}
}
以下是调用工具类实现动态管理数据源的代码:
public void add(DataSourceAddVo vo) {
DataSource bean = BeanUtil.copyProperties(vo,DataSource.class);
//测试链接是否成功
boolean isConnect = checkDataSource(bean);
AssertUtils.assertIsTrue(isConnect,"无法链接数据库");
//添加数据源
Set<String> dbSet = this.addDataSource(bean);
//切换刚刚添加的数据源
DynamicDataSourceContextHolder.push(bean.getCode());
//执行业务方法
DynamicDataSourceContextHolder.poll();
handleAdd(bean,tableList,allVehicleList);
}
private Set<String> addDataSource(DataSource ds){
DataSourceDTO dto = buildDataSourceDTO(ds);
return dynamicDataSourcesUtil.addDataSource(dto);
}
private DataSourceDTO buildDataSourceDTO(DataSource ds){
DataSourceDTO dto = new DataSourceDTO()
.setPoolName(ds.getCode())
.setUsername(ds.getUsername())
.setPassword(ds.getPassword())
.setUrl(ds.getDbUrl())
.setDriverClassName(Constants.MYSQL_DRIVER);
return dto;
}
注:添加数据源必须检查数据源的连通性,否则会一直打印数据源错误。