Hello 小伙伴们大家好, 今天我们一起谈论一下, 如何实现动态数据源呢? Let's go, 让我们开始吧。本期只讨论动态数据源的实现方式。只提出应用场景,不对应用场景做介绍,应用场景放在下期。
一、应用场景
- 主从复制
- 读写分离
- 分库分表
主从复制与读写分离通常是一起使用的。
二、实现方式
1、技术栈
- SpringBoot 2.6.13
- Druid 1.2.16
- Spring JDBC
- MySQL 8.0
- AOP动态代理
2、Maven 依赖
下边依赖放入同一个pom.xml
文件就可以
xml
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3、数据库结构
数据库结构非常简单,主要实现思想,复杂的都是同一个操作方式。

(1)数据库:db_dynamic1
sql
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
BEGIN;
INSERT INTO `tb_user` (`id`, `username`) VALUES (1, '张三');
INSERT INTO `tb_user` (`id`, `username`) VALUES (2, '李四');
INSERT INTO `tb_user` (`id`, `username`) VALUES (3, '王五');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
(2)数据库:db_dynamic2
sql
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
BEGIN;
INSERT INTO `tb_user` (`id`, `username`) VALUES (1, 'Vker');
INSERT INTO `tb_user` (`id`, `username`) VALUES (2, 'Jack');
INSERT INTO `tb_user` (`id`, `username`) VALUES (3, 'Lucy');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
4、application.yml 配置文件
将下面配置文件的url
, username
, password
换成你自己的
yaml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db_dynamic1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: root_root
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db_dynamic2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: root_root
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
5、定义一个 DataSources 注解
这个注解主要用于多数据源的选择,默认是 master
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSources {
String value() default "master";
}
6、配置一下动态数据源
这个类主要用于配置多数据源,不要忘记@Primary
注解,否则会报错。
java
@Component
@Primary
public class DynamicDataSources extends AbstractRoutingDataSource {
public static ThreadLocal<String> name = new ThreadLocal<>();
@Resource
DataSource master;
@Resource
DataSource slave;
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
@Override
public void afterPropertiesSet() {
Map<Object, Object> ds = new HashMap<>();
ds.put("master", master);
ds.put("slave", slave);
super.setTargetDataSources(ds);
super.setDefaultTargetDataSource(master);
super.afterPropertiesSet();
}
}
7、写一个基于 DataSources 注解的动态代理
这个动态代理用于检查类或者方法上的@DataSources
注解,取到注解中的value
值,将值传入动态数据源配置类DynamicDataSources
的ThreadLocal
中,用于选择数据源。
java,
@Component
@Aspect
public class DynamicDataSourcesAspect {
Logger logger = LoggerFactory.getLogger(DynamicDataSourcesAspect.class);
@Before("@annotation(dataSources)")
public void before(DataSources dataSources) {
String value = dataSources.value();
DynamicDataSources.name.set(value);
logger.info("进入AOP代理: {}", value);
}
}
8、再来写一个读数据库配置信息的配置类
这个配置类主要将application.yml
中的数据库配置信息的配置信息进行加载,然后使用数据源事务管理器DataSourceTransactionManager
进行注册。
java
@Configuration
public class DynamicDataSourcesConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource master() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DataSource slave() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("master") DataSource master) {
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(master);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("slave") DataSource slave) {
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(slave);
return dataSourceTransactionManager;
}
}
重点的都做完了,接下来就写一下基本的业务层代码吧,注释就不写了,我相信小伙伴们都能看懂。
1、实体类
java
public class User {
private Long id;
private String username;
// 省略了 getter setter
}
2、Dao 数据层
java
public interface UserMapper {
List<User> selectAllUser();
}
java
@Repository
public class UserMapperImpl implements UserMapper {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public List<User> selectAllUser() {
String sql = "select * from tb_user";
return jdbcTemplate.query(sql,
new BeanPropertyRowMapper<>(User.class));
}
}
3、Service 服务层
java
public interface UserService {
List<User> list();
}
java
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public List<User> list() {
return userMapper.selectAllUser();
}
}
4、Controller 控制层
这里说一下,你可以将注解 @DataSources
注解放到你想使用不同数据源的方法上,如下,默认 master
可以不写。
java
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("list1")
public List<User> list1() {
return userService.list();
}
@GetMapping("list2")
@DataSources("slave")
public List<User> lis2t() {
return userService.list();
}
}
5、最后来看一下启动类
需要把 DataSourceAutoConfiguration
排除掉哦
java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDatasourceDemoApplication.class, args);
}
}
6、测试结果
list1接口

list2接口

好了,以上就是动态数据源的实现方式了,欢迎小伙伴们留言,下期我们看看,如何实现主从复制
和读写分离
。
此致