POM依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
application.yml 配置如下
server:
port: 8080
spring:
application:
name: multi-dataSources
datasource:
db1: # 数据库标识前缀 自定义
jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
db2: # 数据库标识前缀 自定义
jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
简单建表测试,库一库二结构相同,数据不一样:
启动类如下,我们要注入自己的数据源,所以需要事先排除SpringBoot数据库的自动配置行为:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan(basePackages = "com.xxx.mapper")
public class MultiDataSourcesApplication {
public static void main(String[] args) {
SpringApplication.run(MultiDataSourcesApplication.class, args);
}
}
需要一个组件类,继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey 方法,这个方法会在确定目标数据源前调用,获得一个标识,从而获取我们配置的数据源,这里用ThreadLocal来保存。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder= new ThreadLocal<String>();
public static void setDataSourceKey(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
在我们的配置类中注入数据源如下:
@SpringBootConfiguration
public class DataSourcesConfig {
@Primary
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource ds1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource ds2() {
return DataSourceBuilder.create().build();
}
// 注入我们的动态数据源
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("db1") DataSource db1, @Qualifier("db2") DataSource db2) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(db1);
// 设置切换所需的所有动态数据源集合
Map<Object, Object> oMap = new HashMap<>();
oMap.put("db1", db1);
oMap.put("db2", db2);
dynamicDataSource.setTargetDataSources(oMap);
return dynamicDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
// 使用我们的动态数据源来构建SqlSessionFactory
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
再接下来就是来动态切换数据源了,这里我们来定义一个切面:
@Aspect
@Component
public class DynamicDattaSourceAspect {
// 在目标方法调用前改变数据源指向
@Before("@annotation(ds)")
public void changeDataSource(ChangeDataSource ds) {
DynamicDataSourceContextHolder.setDataSourceKey(ds.value());
}
// 在目标方法调用后清除线程变量
@After("@annotation(ds)")
public void clearDataSource(ChangeDataSource ds) {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangeDataSource {
String value();
}
最后,就是使用了:
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/db1")
public Object db1() {
return userService.db1();
}
@GetMapping("/db2")
public Object db2() {
return userService.db2();
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
// 加上我们的自定义注解来切换数据源#查询db1的数据
@ChangeDataSource("db1")
public User db1() {
return userMapper.userGet("1");
}
@Override
// 加上我们的自定义注解来切换数据源#查询db2的数据
@ChangeDataSource("db2")
public User db2() {
return userMapper.userGet("1");
}
}
public interface UserMapper {
@Select("select * from user where id = #{id}")
User userGet(@Param("id") String id);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
}
最终测试结果:
测试成功,能够读取不同数据库的数据,此处不涉及事务。