实际开发中,很多项目都是单数据源。但是还是有一些项目需要多数据源的。
比如当项目中存储的数据量很大,一个服务器扛不住,需要将一部分数据存储在另外一个服务器里,然而我们还要每天去访问这些存储的数据。
这篇文章将以自定义注解的方式实现Springboot项目的多数据源配置,使用的数据库为mysql。
搭建MySQL服务
若没有mysql环境可参考 使用Docker搭建MySQL服务来搭建mysql环境。
模拟多数据库
搭建环境后,新建两个数据库,一个当主库,一个当从库。 两个数据库中建相同的一个user表。
image.png
添加数据
image.png
项目实现
项目结构
image.png引入pom文件
<dependencies>
<!-- 添加web启动坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加mybatis依赖坐标 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!-- 添加mysql驱动器坐标 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 添加AOP坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 添加druid数据源坐标 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
动态数据源配置
这里使用的数据源为druid,实现数据源之间的切换用@DataSource自定义注解,配置Aop进行切换 application.yml 配置文件:
server:
port: 8200
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master: # 主数据源
driverClassName: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/master?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
slave: # 从数据源
driverClassName: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/slave?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
mybatis:
mapper-locations: classpath:mapper/*.xml
多数据源配置类
@Configuration
@Component
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master",masterDataSource);
targetDataSources.put("slave", slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
自定义@DataSource注解
/**
* 自定义多数据源注解
*
* @author dzy 2019-12-23 11:43:09
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
作用于方法上,name = "master"代表操作主数据库,name="slave"代表操作从数据库。
Aop切面类配置
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.dynamic.infra.annotation.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if(dataSource == null){
DynamicDataSource.setDataSource("master");
}else {
DynamicDataSource.setDataSource(dataSource.name());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
}
}
}
启动配置
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
public class DynamicDataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDataSourceApplication.class, args);
}
}
测试
controller:
@RestController
@RequestMapping("/v1/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{name}/list")
public List<User> list(@PathVariable("name")String name){
if(name.equals("master")){
return userService.selectUserListWithMaster();
}else{
return userService.selectUserListWithSlave();
}
}
}
service:
public interface UserService {
/**
* 查询用户列表(主库)
*
* @return User
*/
List<User> selectUserListWithMaster();
/**
* 查询用户列表(从库)
*
* @return User
*/
List<User> selectUserListWithSlave();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public List<User> selectUserListWithMaster() {
return userRepository.selectUserListWithMaster();
}
@Override
public List<User> selectUserListWithSlave() {
return userRepository.selectUserListWithSlave();
}
}
repository
public interface UserRepository {
/**
* 查询用户列表(主库)
*
* @return User
*/
List<User> selectUserListWithMaster();
/**
* 查询用户列表(从库)
*
* @return User
*/
List<User> selectUserListWithSlave();
}
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private UserMapper userMapper;
@Override
@DataSource(name = "master")
public List<User> selectUserListWithMaster() {
return userMapper.selectUserList();
}
@Override
@DataSource(name = "slave")
public List<User> selectUserListWithSlave() {
return userMapper.selectUserList();
}
}
mapper
@Mapper
public interface UserMapper {
/**
* 查询用户列表
*
* @return
*/
List<User> selectUserList();
}
<mapper namespace="com.example.dynamic.infra.mapper.UserMapper">
<select id="selectUserList" resultType="com.example.dynamic.domain.User">
SELECT
id,
name
FROM user
</select>
</mapper>
user实体
@Data
public class User {
private Long id;
private String name;
}
效果图
访问master
image.png
访问slave
image.png
网友评论