一、前言
Spring Boot 多数据源,也称为动态数据源,是指在Spring Boot项目中配置多个数据源,以支持不同的数据库访问需求。这种配置方式在项目开发逐渐扩大、单个数据源无法满足项目支撑需求时变得尤为重要。
二、目的
配置多数据源的主要目的是为了满足复杂的业务需求,例如:
①. 当一个系统需要集成多个项目时,配置多数据源可以方便地管理不同项目的数据。
②. 当系统/服务需要对接多个系统,存入多个不同的表时,多数据源配置可以确保数据的安全性和隔离性。
③. 在读写分离的场景中,主库负责增删改操作,从库负责查询操作,通过多数据源配置可以提高系统的性能和稳定性。
三、实现原理
源码展示
就是通过determineTargetDataSource中的determineCurrentLookupKey,它是个抽象方法,我们可以继承这个抽象类,去实现多数据源的切换。
三、代码示例
1. 在application.properties中配置多个数据源的数据库
datasource.master.type=com.alibaba.druid.pool.DruidDataSource
datasource.master.driver-class-name=com.mysql.jdbc.Driver
datasource.master.jdbc-url=jdbc:mysql://127.0.0.1:3306/mysql?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&useSSL=false
datasource.master.username=root
datasource.master.password=root
datasource.slave.type=com.alibaba.druid.pool.DruidDataSource
datasource.slave.driver-class-name=com.mysql.jdbc.Driver
datasource.slave.jdbc-url=jdbc:mysql://127.0.0.1:3306/mysql2?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&useSSL=false
datasource.slave.username=root
datasource.slave.password=root
dataSourceConfig.use=true
2. 定义数据源枚举类
package com.example.yddemo.RoutingData;
public enum DataSourceType {
MASTER("master"), SLAVE("slave");
String value;
private DataSourceType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
3. 定义一个配置类,加载我们的自定义属性值
@Configuration
@ConditionalOnProperty(name = "dataSourceConfig.use", havingValue = "true")
public class DataSourceConfig {
@Bean("masterDatasource")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource defaultDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("slaveDatasource")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
@Bean
@DependsOn({"masterDatasource", "slaveDatasource"})
@Primary
public DynamicDataSource dataSource(DataSource masterDatasource, DataSource slaveDatasource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.value, masterDatasource);
targetDataSources.put(DataSourceType.SLAVE.value, slaveDatasource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(masterDatasource);
return dataSource;
}
@Bean(name = "slaveTransactionManager")
public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4. 定义数据源切换处理类
定义一个类让它继承AbstractRoutingDataSource,并重写determineCurrentLookupKey
public class DynamicDataSource extends AbstractRoutingDataSource {
//用来保存数据源与获取数据源
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public DynamicDataSource() {
}
public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(new HashMap<Object, Object>(targetDataSources));
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
/**
* 创建切换数据源处理类
* @param dataSource
*/
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
5. 为了方便使用,我们自定义注解
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
6. 使用AOP,让我们的自定义注解生效
@Aspect
@Component
public class DataSourceAspect implements Ordered {
@Pointcut("@annotation(com.example.yddemo.RoutingData.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
try {
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource == null) {
DynamicDataSource.setDataSource(DataSourceType.MASTER.getValue());
} else {
DynamicDataSource.setDataSource(dataSource.name());
}
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
}
}
@Override
public int getOrder() {
return 1;
}
}
7. 定义service方法测试,使用自定义注解
实体类
实体类
public class User {
private Integer id;
private String name;
private String phone;
private String email;
// ... get set
}
Mapper接口
@Mapper
public interface UserMapper {
User getUserInfo(Integer id);
}
UserMapper.xml
<mapper namespace="com.example.yddemo.mapper.UserMapper" >
<select id="getUserInfo" resultType="com.example.yddemo.RoutingData.User" >
select
id,name,phone,email
from sys_user where id = #{id}
</select>
</mapper>
Service实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserInfo(Integer id) {
User userInfo = userMapper.getUserInfo(id);
return userInfo;
}
@Override
@DataSource(name = "slave")
public User getSlaveUserInfo(Integer id) {
return userMapper.getUserInfo(id);
}
}
8. Controller里面进行方法调用
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/query")
public User userQuery(Integer id) {
// 业务逻辑
return userService.getUserInfo(id);
}
@GetMapping("/user/slave/query")
public User userQuery1(Integer id) {
// 业务逻辑
return userService.getSlaveUserInfo(id);
}
}
注意:启动类上面加上@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}), 关闭 spring 的自动数据源配置
9. 运行结果
从库数据展示
以上就是关于多数据源切换的全部内容,有AOP,自定义注解,自定义属性配置等方面的知识点。希望对你有所帮助。