欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于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 调用完毕,切换到主库