基于AbstractDataSource实现主从数据库切换
程序员文章站
2022-07-14 09:30:37
...
基于AbstractDataSource实现主从数据库切换
项目背景:
1.DBA通知某一个SQL运行严重超时,告知将读取源头从主库改为从库,减轻主库压力
2.数据库配置上有两个数据源,主从两个配置;
主库:允许读写;从库:只允许读;
主从库数据同步
3.功能已存在,分析运行流程如下
项目流程:
一、使用自定义注释
@RequestMapping(value="/getBrandHisProjectsForM",method=RequestMethod.POST) @ResponseBody @DbReadonly public Response<BrandHisProjectRes> getBrandHisProjectsForM(@RequestBody BrandHisProjectReq brandHisProjectReq){ return getBrandHisProjects(brandHisProjectReq.getBrandId(), brandHisProjectReq.getCpage(), brandHisProjectReq.getPageSize(), ConstantAction.PLATFORM.M); }
1.在Service层或调用Service层方法的上一层方法上添加 @DbReadonly 注释
2.通过此注释标识下面的读SQL的数据源将连接从数据库
二、自定义注释
// 程序运行时起作用 @Retention(RetentionPolicy.RUNTIME) // 使用位置放在方法定义的上面 @Target(ElementType.METHOD) // 文档注释 @Documented public @interface DbReadonly { // 默认值:readonly ,后续的拦截器、数据同步类需要获取此时定义的数据源 String value() default "readonly"; }
三、Spring 切面注入
@Aspect @Component public class DataSourceAspect { // 对应的切面位置 @Pointcut("@annotation(com.xxx.lang.annotation.DbReadonly)") public void dbAspect() { } // 在方法执行前将数据源切换到从库 @Before("dbAspect()") public void before(JoinPoint joinPoint) { DbContextHolder.setReadonly(); } // 在方法执行后将数据源切换到主库 @After("dbAspect()") public void after(JoinPoint joinPoint) { DbContextHolder.setMaster(); } }
四、读取当前的数据源配置
public class DbContextHolder { /** * 线程级参数 */ private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * 动态数据库信息设定 * * @param dbType */ public static void setDbType(String dbType) { contextHolder.set(dbType); } /** * 动态数据库信息取得 * * @return */ public static String getDbType() { return ((String) contextHolder.get()); } /** * 动态数据库信息清空 * * @return */ public static void clearDbType() { contextHolder.remove(); } /** * 设定主库 */ public static void setMaster() { clearDbType(); } /** * 设定主库 */ public static void setReadonly() { setDbType("readonly"); } }
五、Spring-mybatis.xml 配置动态获取数据源
<bean id="dynDataSource" class="com.xxx.db.DynamicDataSource"> <!-- DynamicDataSource 继承 AbstractDataSource ,此处为targetDataSources赋值 --> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 告知目前已有的数据源及对应的key --> <entry value-ref="dataSource" key="master"></entry> <entry value-ref="readonlyDataSource" key="readonly"></entry> </map> </property> <property name="defaultTargetDataSource" ref="dataSource" /> </bean>
六、动态读取当前数据源配置
public class DynamicDataSource extends AbstractRoutingDataSource { /** * 分配动态数据库 */ @Override protected Object determineCurrentLookupKey() { String obj = DbContextHolder.getDbType(); if (obj == null || obj.equalsIgnoreCase("default")) { return "master"; } else { return obj; } } }
七、数据库选择拦截器
// 默认数据源为主库,避免出现空值 public class DbSelectFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { DbContextHolder.setDbType("defalut"); chain.doFilter(request, response); } @Override public void destroy() { } }
八、Spring-mybatis.xml 加载
<!-- sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" <!-- 配置数据源,为动态获取,动态获取中配置两个设置好的数据源配置 --> p:dataSource-ref="dynDataSource" p:typeAliasesPackage="com.xxx.core.model," p:mapperLocations="classpath*:com/**/mapping/*Mapper.xml"> <property name="configLocation" value="classpath:conf/mybatis-config.xml" /> </bean>
项目运行:
1.每一个请求都经过拦截器处理,默认处理成读取主库
2.当在service 中的 method上有使用 @DbReadonly 注释时,Spring 面向切面的标识
在方法执行前将 数据源切换到从库,在方法执行后切换到主库
项目备注:
1.不可多次套用此标签,即在调用 Service 处(对外提供的调用接口,用于对于某一具体需求进行处理,同时调用多个Service 接口)使用,同时在Service 中使用,会导致切换失败
分析:
A 调用 B
A 上使用了此标签,切换到 从库
B 上使用了此标签,切换到 从库,B 方法执行完毕,切换到主库
A 接收B 方法的返回值
A 中其他方法,运行,若此时其他方法上未加此注释,则数据源此时已被切换到了主库上
A 调用完毕,切换到主库
上一篇: PHP实现最简单爬虫原型