实战(Spring|实战:Spring AOP实现多数据源动态切换)
需求背景
去年底,公司项目有一个需求中有个接口需要用到平台、算法、大数据等三个不同数据库的数据进行计算、组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分。
直到今年回来了,这个项目也做得差不多了,这会儿才有时间区仔细看同事的代码,是怎么去实现多数据源动态切换的。
扩展:当业务也来越复杂,数据量越来越庞大时,就可能会对数据库进行分库分表、读写分离等设计来减轻压力、提高系统性能,那么多数据源动态切换势必是必不可少!
经过了一星期零零碎碎的下班时间,从了解原理、实现、优化的过程,自己终于总算是弄出来了,接下来一起看看!
思考
- 如何让Spring知道我们配置了多个数据源?
- 配置了多个数据源后,Spring是如何决定使用哪一个数据源?
- Spring是如何动态切换数据源?
- 配置多数据源信息
spring:
datasource:
local:
database: local
username: root
password:
jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
server:
database: server
username: root
password:
jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
这是我的两个数据库:本地数据库+个人服务器数据库
服务器数据库
文章图片
本地数据库
文章图片
- Spring如何获取配置好的多个数据源信息?
@Value注解获取(实体类需配合@Component),最简单,但当配置信息较多时,写起来比较繁琐
@ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息
@Environment注解从Spring环境中获取,实现较为复杂,本人很少用同事使用的方式是第一种方式,但是我个人觉得这样侵入性较大,每增加一个数据源,就要重新定义变量然后用@Value去重新配置,很麻烦,所以我就选择了第二种方式
通过@ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DBProperties {private HikariDataSource server;
private HikariDataSource local;
}
将所有的数据源加载到Spring中,可供其选择使用
@Slf4j
@Configuration
public class DataSourceConfig {@Autowired
private DBProperties dbProperties;
@Bean(name = "multiDataSource")
public MultiDataSource multiDataSource(){
MultiDataSource multiDataSource = new MultiDataSource();
//1.设置默认数据源
multiDataSource .setDefaultTargetDataSource(dbProperties.getLocal());
//2.配置多数据源
HashMap
如此之后,确实是可以读取YML中的数据源信息,但是总觉得怪怪的。
果然!当我实现了整个功能后,我发现,如果我想要再加一个数据源,我还是得去求改DBProperties和DataSourceConfig这两类的内容,就很烦,我这个人比较懒,所以我就将这部分内容优化了一下:
优化后的YML
spring:
datasource:
names:
- database: dataSource0
username: root
password:
jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
- database: dataSource1
username: root
password:
jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
优化后的DBProperties
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DBProperties {private List DBNames;
}
优化后的DataSourceConfig
@Slf4j
@Configuration
public class DataSourceConfig {@Autowired
private DBProperties dbProperties;
@Bean(name = "multiDataSource")
public MultiDataSource multiDataSource(){
MultiDataSource multiDataSource = new MultiDataSource();
List names = dbProperties.getNames();
if (CollectionUtils.isEmpty(names)){
throw new RuntimeException("please configure the data source!");
}multiDataSource.setDefaultTargetDataSource(names.get(0));
HashMap
这样子,我之后无论配置了多少个数据源信息,我都不需要再去修改配置代码
- Spring如何选择使用数据源?
通过继承AbstractRoutingDataSource接口,重写determineCurrentLookupKey方法,选择具体的数据源
@Slf4j
public class MultiDataSource extends AbstractRoutingDataSource {@Override
protected Object determineCurrentLookupKey() {return MultiDataSourceHolder.getDatasource();
}}
利用ThreadLocal实现数据源线程隔离
public class MultiDataSourceHolder {private static final ThreadLocal threadLocal =new ThreadLocal<>();
public static void setDatasource(String datasource){
threadLocal.set(datasource);
}public static String getDatasource(){
return threadLocal.get();
}public static void clearDataSource(){
threadLocal.remove();
}}
准备工作做好,下面开始将动态切换操作串联起来
利用AOP切面+自定义注解
自定义注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiDataSource {String DBName();
}
AOP切面
@Slf4j
@Aspect
@Component
public class DataSourceAspect {@Pointcut(value = "https://www.it610.com/article/@within(com.xiaozhao.base.aop.annotation.MultiDataSource) || @annotation(com.xiaozhao.base.aop.annotation.MultiDataSource)")
public void dataSourcePointCut(){}@Before("dataSourcePointCut() && @annotation(multiDataSource)")
public void before(MultiDataSource multiDataSource){String dbName = multiDataSource.DBName();
if (StringUtils.hasLength(dbName)){MultiDataSourceHolder.setDatasource(multiDataSource.DBName());
log.info("current dataSourceName ====== "+dbName);
}else {log.info("switch datasource fail, use default, or please configure the data source for the annotations,");
}
}@After("dataSourcePointCut()")
public void after(){
MultiDataSourceHolder.clearDataSource();
}
}
好了!功能依然实现,打完收工!
。。。。
如果我工作中也这样,估计要被测试打死!为了敷衍一下,来进行一下测试
一套代码直接打完:
Controller+Service+Dao
@RestController
@RequestMapping("user")
public class UserController {@Autowired
private UserService userService;
@GetMapping("/info")
public UserVO getUser(){
return userService.creatUser();
}
}public interface UserService {
UserVO creatUser();
UserVO setUserInfo(String phone);
}@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class UserServiceImpl implements UserService {@Autowired
private UserMapper userMapper;
@Autowired
private InfoMapper infoMapper;
@Override
public UserVO creatUser() {
UserVO userVO = userMapper.getUserInfoMapper();
return ((UserService) AopContext.currentProxy()).setUserInfo(userVO.getPhone());
}@MultiDataSource(DBName = "dataSource-1")
public UserVO setUserInfo(String phone) {UserVO userInfo = infoMapper.getUserInfo();
UserVO user = new UserVO();
user.setUserName(userInfo.getUserName());
user.setPassword(userInfo.getPassword());
user.setAddress(userInfo.getAddress());
user.setPhone(phone);
return user;
}
}@Mapper
public interface InfoMapper {@Select("select id,user_name as userName,password,phone,address from test_user")
UserVO getUserInfo();
}@Mapper
public interface UserMapper {@Select("select id,user_name as userName,password,phone from user")
UserVO getUserInfoMapper();
}
测试结果:红框数据来自于服务器数据库,绿框数据来自于本地数据库
文章图片
遇到的问题
- 同一个类中,A方法调用B方法用
AopContext.currentProxy()
报错问题:在类上加@EnableAspectJAutoProxy(exposeProxy = true)
————解决! - 切面时,用JoinPoint获取方法,判断是否被注解修饰(虽然纯属多余)结果为false————有待考究!
【实战(Spring|实战:Spring AOP实现多数据源动态切换)】小菜鸡的学习成长之路,拒绝无味的CRUD,每过一段时间,就会把工作中用到,或者别人实现的功能解析、实现,并分享!下一篇,Redission实现分布式锁
推荐阅读
- springboot-jta-atomikos多数据源事务管理
- 基于区块链技术的超级账本(Hyperledger)|基于区块链技术的超级账本(Hyperledger) - 从理论到实战
- Spring|Spring Boot 应用使用 application.yml 和 application.properties 的区别
- 图文详解 Spring AOP,看完必懂。。
- Gitlab|Gitlab CI/CD教程及npm包构建发布实战
- Spring|Spring boot测试找不到SpringRunner.class的问题
- 解决Spring|解决Spring Security的权限配置不生效问题
- 使用SpringSecurity设置角色和权限的注意点
- 项目实战|SpringBoot个人博客从无到有项目搭建——实战综合介绍
- springboot启动前执行方法的四种方式总结