Springboot多数据源配置之整合dynamic-datasource方式
在实际的应用开发中,我们往往需要连接多个数据库来存储不同的数据,而Springboot提供了多种方式来实现多数据源配置,其中一种方便易用的方式就是使用dynamic-datasource这个开源的库。
本文将介绍如何使用dynamic-datasource来配置Springboot的多数据源,并提供两个示例来演示如何在应用中使用它。
集成dynamic-datasource
- 在
pom.xml
中添加以下依赖:
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
- 配置application.yml文件,设置数据源信息:
yaml
dynamic-datasource:
datasource:
master:
url: jdbc:mysql://localhost:3306/master
username: root
password: root
slave:
url: jdbc:mysql://localhost:3306/slave
username: root
password: root
在上述示例中,我们配置了两个数据源,一个是master
,另一个是slave
。
-
在Springboot主类上添加注解
@EnableDynamicDataSource
。 -
在需要使用数据源的地方使用注解
@DS
来指定数据源名称。默认的数据源名称为master
。
```java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@DS("slave")
@Override
public List<User> listUsers() {
return userMapper.listUsers();
}
// 省略其他方法
}
```
示例1:应用中使用单例的动态数据源
在这个示例中,我们将使用dynamic-datasource来配置一个单例的动态数据源,并演示如何在应用中使用它。
- 配置数据源信息
在application.yml文件中配置数据源信息,同时也配置了一个默认数据源:
```yaml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
dynamic-datasource:
datasource:
db1:
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
db2:
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
primary: db1
```
我们配置了三个数据源,一个是默认数据源spring.datasource
,另外两个是db1
和db2
。
- 类和注解的配置
我们需要创建一个DynamicDataSourceConfig
类,用于动态创建数据源。同时,使用注解@Import
来将这个类导入进来。
java
@Configuration
@Import(DynamicDataSourceRegister.class)
public class DynamicDataSourceConfig {
}
动态数据源的注册类DynamicDataSourceRegister
如下所示:
```java
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private ResourceLoader resourceLoader;
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<>();
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注册默认数据源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", DataSource.class);
dsMap.put("driverClassName", "com.mysql.jdbc.Driver");
dsMap.put("url", "jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8");
dsMap.put("username", "root");
dsMap.put("password", "root");
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource);
// 注册其它数据源
Map<String, Object> dataSourceMap = (Map<String, Object>) YamlUtils.load("datasource.yml");
// 这里也可以使用JdbcUtils.execute()来获取数据源信息,而不是从YAML配置文件中获取
for (String key : dataSourceMap.keySet()) {
Map<String, Object> ds = (Map<String, Object>) dataSourceMap.get(key);
DataSource dataSource = buildDataSource(ds);
customDataSources.put(key, dataSource);
dataBinder(dataSource);
}
// 注册动态数据源
Map<String, Object> targetDataSources = new HashMap<>();
targetDataSources.put("dataSource", defaultDataSource);
targetDataSources.putAll(customDataSources);
// 添加数据源
DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
RootBeanDefinition beanDefinition = new RootBeanDefinition(DynamicDataSource.class);
// 给bean添加属性
beanDefinition.getPropertyValues().addPropertyValue("defaultTargetDataSource", defaultDataSource);
beanDefinition.getPropertyValues().addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("Dynamic DataSource Registry");
}
/**
* 创建数据源
*
* @param dsMap
* @return
*/
private DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null) {
type = DruidDataSource.class;
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) type;
} catch (Exception e) {
throw new IllegalArgumentException("Invalid dataSource type", e);
}
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password);
return factory.type(dataSourceType).build();
}
/**
* 为数据源绑定更多属性
*
* @param dataSource
*/
private void dataBinder(DataSource dataSource) {
if (dataSource instanceof DruidDataSource) {
WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(wallConfig());
List<Filter> filters = new ArrayList<>();
filters.add(wallFilter);
((DruidDataSource) dataSource).setProxyFilters(filters);
}
}
/**
* 加载wallConfig
*
* @return
*/
private WallConfig wallConfig() {
WallConfig wallConfig = new WallConfig();
wallConfig.setMultiStatementAllow(true);
wallConfig.setNoneBaseStatementAllow(true);
return wallConfig;
}
}
```
动态数据源类DynamicDataSource
如下所示:
```java
public class DynamicDataSource extends AbstractRoutingDataSource {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.debug("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
```
- Service层代码中使用所需的数据源
现在,我们可以在Service层的代码中使用所需的数据源。在使用的方法上添加注解@DS
来指定使用哪个数据源。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@DS("db1")
@Override
public List<User> listUsersFromDb1() {
return userMapper.listUsers();
}
@DS("db2")
@Override
public List<User> listUsersFromDb2() {
return userMapper.listUsers();
}
// 省略其他方法
}
这个示例中,当我们调用listUsersFromDb1()
方法时,它会使用db1
数据源来查询数据;而当我们调用listUsersFromDb2()
方法时,它会使用db2
数据源来查询数据。
示例2:应用中使用多例的动态数据源
在这个示例中,我们将使用dynamic-datasource来配置一个多例的动态数据源,并演示如何在应用中使用它。
- 配置数据源信息
在application.yml文件中配置数据源信息:
yaml
dynamic-datasource:
datasource:
db1:
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
db2:
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
primary: db1
我们配置了两个数据源,db1
和db2
。默认的数据源为db1
。
- 注册动态数据源,使其成为多例对象
为了使用多例的数据源,我们需要将动态数据源的注册类DynamicDataSourceRegister
中的@Configuration
注解移除。因此,我们需要将它与DynamicDataSourceConfig
分开。
DynamicDataSourceConfig
类:
```java
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "dynamic-datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource dataSource() {
return dataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
```
DynamicDataSourceRegister
类:
```java
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private ResourceLoader resourceLoader;
private Environment environment;
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<>();
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注册默认数据源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", DruidDataSource.class);
dsMap.put("driverClassName", "com.mysql.jdbc.Driver");
dsMap.put("url", "jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8");
dsMap.put("username", "root");
dsMap.put("password", "root");
defaultDataSource = buildDataSource(dsMap);
// 注册其它数据源
Map<Object, Object> dataSourceMap = new HashMap<>();
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "dynamic-datasource.datasource.");
String[] dsNames = propertyResolver.getProperty("names", String[].class);
Arrays.stream(dsNames)
.forEach(dsName -> {
Map<String, Object> subProperties = propertyResolver.getSubProperties(dsName + ".");
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(subProperties.get("driver-class-name").toString())
.url(subProperties.get("url").toString())
.username(subProperties.get("username").toString())
.password(subProperties.get("password").toString())
.build();
customDataSources.put(dsName, dataSource);
dataSourceMap.put(dsName, dataSource);
});
// 添加数据源
DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
// 创建动态数据源
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicRoutingDataSource.class);
beanDefinition.setSynthetic(true);
// 给bean添加属性
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", dataSourceMap);
registry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("Dynamic DataSource Registry");
}
/**
* 创建数据源
*
* @param dsMap
* @return
*/
private DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null) {
type = DruidDataSource.class;
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) type;
} catch (Exception e) {
throw new IllegalArgumentException("Invalid dataSource type", e);
}
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password);
return factory.type(dataSourceType).build();
}
}
```
DynamicRoutingDataSource
类:
```java
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
```
- Service层代码中使用所需的数据源
与示例1相似,我们可以在Service层的代码中使用所需的数据源。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@DS("db1")
@Override
public List<User> listUsersFromDb1() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
}
@DS("db2")
@Override
public List<User> listUsersFromDb2() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
}
// 省略其他方法
}
此示例中的方法也类似于示例1。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Springboot多数据源配置之整合dynamic-datasource方式 - Python技术站