Spring-Boot + AOP实现多数据源动态切换

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要在一个项目中配置多数据源,并且能够动态切换。

设计总体思路

Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源。

步骤

一、多数据源配置

在application.properties中,我们的配置是这样的


#主数据源druid.master.url=jdbc:mysql://url/masterdb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNulldruid.master.username=xxxdruid.master.password=123druid.master.driver-class-name=com.mysql.jdbc.Driverdruid.master.max-wait=5000druid.master.max-active=100druid.master.test-on-borrow=truedruid.master.validation-query=SELECT 1#从数据源druid.slave.url=jdbc:mysql://url/slavedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNulldruid.slave.username=xxxdruid.slave.password=123druid.slave.driver-class-name=com.mysql.jdbc.Driverdruid.slave.max-wait=5000druid.slave.max-active=100druid.slave.test-on-borrow=truedruid.slave.validation-query=SELECT 1

读取配置

	<!-- master数据源 --><bean primary="true" id="masterdb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性 url、user、password --><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="${druid.master.url}"/><property name="username" value="${druid.master.username}"/><property name="password" value="${druid.master.password}"/><!-- 配置初始化最大 --><property name="maxActive" value="${druid.master.max-active}"/><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${druid.master.max-wait}"/><property name="validationQuery" value="${druid.master.validation-query}"/><property name="testOnBorrow" value="${druid.master.test-on-borrow}"/></bean><!-- slave数据源 --><bean primary="true" id="slavedb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性 url、user、password --><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="${druid.slave.url}"/><property name="username" value="${druid.slave.username}"/><property name="password" value="${druid.slave.password}"/><!-- 配置初始化大小、最小、最大 --><property name="maxActive" value="${druid.slave.max-active}"/><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${druid.slave.max-wait}"/><property name="validationQuery" value="${druid.slave.validation-query}"/><property name="testOnBorrow" value="${druid.slave.test-on-borrow}"/></bean><!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 --><bean id="dataSource" class="datasource.DynamicDataSource"><property name="targetDataSources"><map key-type="java.lang.String"><entry key="slave" value-ref="slavedb"/><entry key="master" value-ref="masterdb"/></map></property><property name="defaultTargetDataSource" ref="masterdb"/></bean><!-- Spring JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource" /></bean><!-- Spring事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /></bean><bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"><property name="transactionManager" ref="transactionManager"/></bean><tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2" /><!-- depositdbSqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><property name="mapperLocations" value="classpath*:mapper-xxdb/*Mapper*.xml" /></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="xxdb.mapper"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean>

二. 数据源动态切换类

动态数据源切换是基于AOP的,所以我们需要声明一个AOP切面,并在切面前做数据源切换,切面完成后移除数据源名称。

	@Order(1)   //设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)@Aspect@Componentpublic class DataSourceAspect {private Logger logger = LoggerFactory.getLogger(this.getClass());//切点@Pointcut("execution(* com.xxx.service.*.*(..))")public void aspect() { }@Before("aspect()")private void before(JoinPoint point) {Object target = point.getTarget();String method = point.getSignature().getName();Class<?> classz = target.getClass();// 获取目标类Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();try {Method m = classz.getMethod(method, parameterTypes);if (m != null && m.isAnnotationPresent(MyDataSource.class)) {MyDataSource data = m.getAnnotation(MyDataSource.class);logger.info("method :{},datasource:{}",m.getName() ,data.value().getName());JdbcContextHolder.putDataSource(data.value().getName());// 数据源放到当前线程中}} catch (Exception e) {logger.error("get datasource error ",e);//默认选择masterJdbcContextHolder.putDataSource(DataSourceType.Master.getName());// 数据源放到当前线程中}}@AfterReturning("aspect()")public void after(JoinPoint point) {JdbcContextHolder.clearDataSource();}}

三、数据源管理类

	public class JdbcContextHolder {private final static ThreadLocal<String> local = new ThreadLocal<>();public static void putDataSource(String name) {local.set(name);}public static String getDataSource() {return local.get();}public static void clearDataSource() {local.remove();}}

四、动态数据源

spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。

	public class DynamicDataSource extends AbstractRoutingDataSource {private Logger logger = LoggerFactory.getLogger(this.getClass());@Overrideprotected Object determineCurrentLookupKey() {String dataSource = JdbcContextHolder.getDataSource();logger.info("数据源为{}",dataSource);return dataSource;}}

五、数据源注解和枚举

我们切换数据源时,一般都是在调用具体接口的方法前实现,所以我们定义一个方法注解,当AOP检测到方法上有该注解时,根据注解中value对应的名称进行切换。

	@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyDataSource {DataSourceType value();}

	public enum  DataSourceType {// 主表Master("master"),// 从表Slave("slave");private String name;private DataSourceType(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}

六、切点注解

由于我们的动态数据源配置了默认库,所以如果方法是操作默认库的可以不需要注解,如果要操作非默认数据源,我们需要在方法上添加@MyDataSource("数据源名称")注解,这样就可以利用AOP实现动态切换了

	@Componentpublic class xxxServiceImpl {@Resourceprivate XxxMapperExt xxxMapperExt;@MyDataSource(value= DataSourceType.Slave)public List<Object> getAll(){return xxxMapperExt.getAll();}}

转载于:https://my.oschina.net/stephenzhang/blog/1786156

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/390862.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 1738. 找出第 K 大的异或坐标值

本文正在参加「Java主题月 - Java 刷题打卡」&#xff0c;详情查看 活动链接 题目 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素…

商业数据科学

数据科学 &#xff0c; 意见 (Data Science, Opinion) “There is a saying, ‘A jack of all trades and a master of none.’ When it comes to being a data scientist you need to be a bit like this, but perhaps a better saying would be, ‘A jack of all trades and …

leetcode 692. 前K个高频单词

题目 给一非空的单词列表&#xff0c;返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c;按字母顺序排序。 示例 1&#xff1a; 输入: ["i", "love", "leetcode", "…

数据显示,中国近一半的独角兽企业由“BATJ”四巨头投资

中国的互联网行业越来越有被巨头垄断的趋势。百度、阿里巴巴、腾讯、京东&#xff0c;这四大巨头支撑起了中国近一半的独角兽企业。CB Insights日前发表了题为“Nearly Half Of China’s Unicorns Backed By Baidu, Alibaba, Tencent, Or JD.com”的数据分析文章&#xff0c;列…

Java的Servlet、Filter、Interceptor、Listener

写在前面&#xff1a; 使用Spring-Boot时&#xff0c;嵌入式Servlet容器可以通过扫描注解&#xff08;ServletComponentScan&#xff09;的方式注册Servlet、Filter和Servlet规范的所有监听器&#xff08;如HttpSessionListener监听器&#xff09;。 Spring boot 的主 Servlet…

leetcode 1035. 不相交的线(dp)

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足满足&#xff1a; nums1[i] nums2[j] 且绘制的直线不与任何其他连线&#xff08;非水平线&#x…

SPI和RAM IP核

学习目的&#xff1a; &#xff08;1&#xff09; 熟悉SPI接口和它的读写时序&#xff1b; &#xff08;2&#xff09; 复习Verilog仿真语句中的$readmemb命令和$display命令&#xff1b; &#xff08;3&#xff09; 掌握SPI接口写时序操作的硬件语言描述流程&#xff08;本例仅…

个人技术博客Alpha----Android Studio UI学习

项目联系 这次的项目我在前端组&#xff0c;负责UI&#xff0c;下面简略讲下学到的内容和使用AS过程中遇到的一些问题及其解决方法。 常见UI控件的使用 1.TextView 在TextView中&#xff0c;首先用android:id给当前控件定义一个唯一标识符。在活动中通过这个标识符对控件进行事…

数据科学家数据分析师_站出来! 分析人员,数据科学家和其他所有人的领导和沟通技巧...

数据科学家数据分析师这一切如何发生&#xff1f; (How did this All Happen?) As I reflect on my life over the past few years, even though I worked my butt off to get into Data Science as a Product Analyst, I sometimes still find myself begging the question, …

react-hooks_在5分钟内学习React Hooks-初学者教程

react-hooksSometimes 5 minutes is all youve got. So in this article, were just going to touch on two of the most used hooks in React: useState and useEffect. 有时只有5分钟。 因此&#xff0c;在本文中&#xff0c;我们仅涉及React中两个最常用的钩子&#xff1a; …

分析工作试用期收获_免费使用零编码技能探索数据分析

分析工作试用期收获Have you been hearing the new industry buzzword — Data Analytics(it was AI-ML earlier) a lot lately? Does it sound complicated and yet simple enough? Understand the logic behind models but dont know how to code? Apprehensive of spendi…

select的一些问题。

这个要怎么统计类别数呢&#xff1f; 哇哇哇 解决了。 之前怎么没想到呢&#xff1f;感谢一楼。转载于:https://www.cnblogs.com/AbsolutelyPerfect/p/7818701.html

重学TCP协议(12)SO_REUSEADDR、SO_REUSEPORT、SO_LINGER

1. SO_REUSEADDR 假如服务端出现故障&#xff0c;主动断开连接以后&#xff0c;需要等 2 个 MSL 以后才最终释放这个连接&#xff0c;而服务重启以后要绑定同一个端口&#xff0c;默认情况下&#xff0c;操作系统的实现都会阻止新的监听套接字绑定到这个端口上。启用 SO_REUSE…

残疾科学家_数据科学与残疾:通过创新加强护理

残疾科学家Could the time it takes for you to water your houseplants say something about your health? Or might the amount you’re moving around your neighborhood reflect your mental health status?您给植物浇水所需的时间能否说明您的健康状况&#xff1f; 还是…

Linux 网络相关命令

1. telnet 1.1 检查端口是否打开 执行 telnet www.baidu.com 80&#xff0c;粘贴下面的文本&#xff08;注意总共有四行&#xff0c;最后两行为两个空行&#xff09; telnet [domainname or ip] [port]例如&#xff1a; telnet www.baidu.com 80 如果这个网络连接可达&…

spss23出现数据消失_改善23亿人口健康数据的可视化

spss23出现数据消失District Health Information Software, or DHIS2, is one of the most important sources of health data in low- and middle-income countries (LMICs). Used by 72 different LMIC governments, DHIS2 is a web-based open-source platform that is used…

01-hibernate注解:类级别注解,@Entity,@Table,@Embeddable

Entity Entity:映射实体类 Entity(name"tableName") name:可选&#xff0c;对应数据库中一个表&#xff0c;若表名与实体类名相同&#xff0c;则可以省略。 注意&#xff1a;使用Entity时候必须指定实体类的主键属性。 第一步&#xff1a;建立实体类&#xff1a; 分别…

COVID-19研究助理

These days scientists, researchers, doctors, and medical professionals face challenges to develop answers to their high priority scientific questions.如今&#xff0c;科学家&#xff0c;研究人员&#xff0c;医生和医学专家面临着挑战&#xff0c;无法为其高度优先…

Go语言实战 : API服务器 (8) 中间件

为什么需要中间件 我们可能需要对每个请求/返回做一些特定的操作&#xff0c;比如 记录请求的 log 信息在返回中插入一个 Header部分接口进行鉴权 这些都需要一个统一的入口。这个功能可以通过引入 middleware 中间件来解决。Go 的 net/http 设计的一大特点是特别容易构建中间…

缺失值和异常值的识别与处理_识别异常值-第一部分

缺失值和异常值的识别与处理&#x1f4c8;Python金融系列 (&#x1f4c8;Python for finance series) Warning: There is no magical formula or Holy Grail here, though a new world might open the door for you.警告 &#xff1a; 这里没有神奇的配方或圣杯&#xff0c;尽管…