DynamicDataSource是一个数据源路由器,可以根据上下文动态选择数据源。可以在每个请求或线程中将数据源设置为当前需要使用的数据.
1. 创建一个DynamicDataSource类来实现数据源路由逻辑
创建一个DynamicDataSource类,它继承自AbstractRoutingDataSource。在该类中重写**determineCurrentLookupKey()**方法,该方法返回一个字符串,用于指示当前要使用哪个数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceName();
}
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
}
2. 创建DynamicDataSourceContextHolder线程安全类
在多线程环境下,如果多个线程同时访问同一个方法,并且每个线程要使用不同的数据源,那么就需要对数据源进行动态切换。如果在方法中使用一个共享的变量来存储当前要使用的数据源名称,那么就会存在线程安全问题,可能会导致不同线程之间的数据源切换混乱,或者数据源切换不成功的情况发生。
为了解决这个问题,可以使用ThreadLocal来存储当前要使用的数据源名称。ThreadLocal是一种线程本地存储机制,它可以为每个线程提供一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本,而不会影响其他线程的变量副本。
在使用DynamicDataSource进行数据源切换时,每个线程都可以通过ThreadLocal来独立地设置和获取当前要使用的数据源名称,避免了多个线程之间数据源切换的混乱和不成功的情况。同时,在方法执行完毕后,使用ThreadLocal也可以避免内存泄漏问题。
使用ThreadLocal来存储当前要使用的数据源名称。**setDataSource()**方法用于设置当前要使用的数据源名称,**getDataSource()**方法用于获取当前要使用的数据源名称,**clearDataSource()**方法用于清除当前要使用的数据源名称
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置当前数据源的名称
*/
public static void setDataSourceName(String dataSourceName) {
contextHolder.set(dataSourceName);
}
/**
* 获取当前数据源的名称
*/
public static String getDataSourceName() {
return contextHolder.get();
}
/**
* 清除当前数据源的名称
*/
public static void clearDataSourceName() {
contextHolder.remove();
}
}
3.创建多数据源配置
通过yml配置,将多数据源获取到MutilDataSourceProperties中
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://****:3306/emp_ts?nullNamePatternMatchesAll=true&tinyInt1isBit=false&useSSL=false&nullCatalogMeansCurrent=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
mutil-datasource:
connection:
- dbName: dataSource1
dbDriver: com.mysql.cj.jdbc.Driver
dbUrl: jdbc:mysql://****:3306/emp_ts_1?nullNamePatternMatchesAll=true&tinyInt1isBit=false&useSSL=false&nullCatalogMeansCurrent=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
dbUsername: root
dbPassword: root
public class DbConnection {
private String dbName;
private String dbDialect;
private String dbDriver;
private String dbUrl;
private String dbUsername;
private String dbPassword;
public DbConnection() {
}
public String getDbName() {
return this.dbName;
}
public String getDbDialect() {
return this.dbDialect;
}
public String getDbDriver() {
return this.dbDriver;
}
public String getDbUrl() {
return this.dbUrl;
}
public String getDbUsername() {
return this.dbUsername;
}
public String getDbPassword() {
return this.dbPassword;
}
public void setDbName(String dbName) {
this.dbName = dbName;
}
public void setDbDialect(String dbDialect) {
this.dbDialect = dbDialect;
}
public void setDbDriver(String dbDriver) {
this.dbDriver = dbDriver;
}
public void setDbUrl(String dbUrl) {
this.dbUrl = dbUrl;
}
public void setDbUsername(String dbUsername) {
this.dbUsername = dbUsername;
}
public void setDbPassword(String dbPassword) {
this.dbPassword = dbPassword;
}
}
@Configuration
@ConfigurationProperties(
prefix = "mutil-datasource"
)
public class MutilDataSourceProperties {
private List<DbConnection> connection = new ArrayList();
public MutilDataSourceProperties() {
}
public List<DbConnection> getConnection() {
return this.connection;
}
public void setConnection(List<DbConnection> connection) {
this.connection = connection;
}
}
4.创建DataSourceConfig
在配置类中创建默认数据源及获取其他多数据源进行创建,默认数据源为spring.datasource下配置的数据源
@Configuration
public class DataSourceConfig {
@Resource
private MutilDataSourceProperties mutilDataSourceProperties;
public DataSourceConfig() {
}
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource hikariDataSource(DataSourceProperties properties) {
return DataSourceBuilder.create(properties.getClassLoader()).driverClassName(properties.determineDriverClassName()).url(properties.determineUrl()).username(properties.determineUsername()).password(properties.determinePassword()).build();
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource(DataSource hikariDataSource) {
Map<Object, Object> targetDataSources = this.createTargetDataSource();
return new DynamicDataSource(hikariDataSource, targetDataSources);
}
private Map<Object, Object> createTargetDataSource() {
Map<Object, Object> targetDataSources = new HashMap();
List<DbConnection> connections = this.mutilDataSourceProperties.getConnection();
connections.forEach(e -> {
DataSource dataSource = this.createDataSource(e);
targetDataSources.put(e.getDbName(), dataSource);
});
return targetDataSources;
}
public DataSource createDataSource(DbConnection connection) {
return DataSourceBuilder.create().driverClassName(connection.getDbDriver()).url(connection.getDbUrl())
.username(connection.getDbName()).password(connection.getDbPassword()).build();
}
}
5. 使用
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
DynamicDataSource.setDataSource("dataSource1");
userMapper.insert(user);
}
@Override
public void updateUser(User user) {
DynamicDataSource.setDataSource("dataSource1");
userMapper.updateById(user);
}
}
6.优缺点
优点:
- 简单易用:使用 DynamicDataSource 和 ThreadLocal 进行动态数据源切换,配置相对简单,易于上手。
- 可扩展性强:通过继承 AbstractRoutingDataSource 类,可以实现自定义的数据源路由策略。并且,由于采用了抽象类,扩展性也比较好。
- 线程安全:使用 ThreadLocal 来存储当前数据源的名称,可以避免多线程之间数据源切换的混乱和不成功的情况。
缺点:
-
不能同时访问多个数据源:在使用 DynamicDataSource 进行动态数据源切换时,同一时间只能访问一个数据源,不能同时访问多个数据源。
-
执行效率稍低:在使用 DynamicDataSource 进行动态数据源切换时,每次数据源切换都需要进行一次路由选择,会稍微影响执行效率。
-
不支持事务嵌套:在使用 DynamicDataSource 进行动态数据源切换时,如果在一个事务中需要访问多个数据源,那么就需要进行事务管理,而 DynamicDataSource 并不支持事务嵌套。
综上所述,使用 DynamicDataSource 和 ThreadLocal 进行动态数据源切换,优点是简单易用、可扩展性强、线程安全,缺点是不能同时访问多个数据源、执行效率稍低、不支持事务嵌套。在具体使用时需要根据业务场景进行选择。