当前位置: 移动技术网 > IT编程>开发语言>Java > 数据源管理 | 主从库动态路由,AOP模式读写分离

数据源管理 | 主从库动态路由,AOP模式读写分离

2020年04月01日  | 移动技术网IT编程  | 我要评论

本文源码:github·点这里 || gitee·点这里

一、多数据源应用

1、基础描述

在相对复杂的应用服务中,配置多个数据源是常见现象,例如常见的:配置主从数据库用来写数据,再配置一个从库读数据,这种读写分离模式可以缓解数据库压力,提高系统的并发能力和稳定性,执行效率。

2、核心api

在处理这种常见问题,要学会查询服务基础框架的api,说直白点就是查询spring框架的api(工作几年,还没用过spring之外的框架搭建环境),这种常用的业务模式,基本上spring都提供了api支持。

核心api:abstractroutingdatasource

底层维护map容器,用来保存数据源集合,提供一个抽象方法,实现自定义的路由策略。

@nullable
private map<object, datasource> resolveddatasources;
@nullable
protected abstract object determinecurrentlookupkey();

补刀一句:为何框架的原理很难通过一篇文章看明白?因为使用的不多,基本意识没有形成,熟悉框架原理的基本要求:对框架的各种功能都熟悉,经常使用,自然而然的就明白了,盐大晒的久,咸鱼才够味。

二、数据源路由

1、数据源管理

配置两个数据源

spring:
  datasource:
    type: com.alibaba.druid.pool.druiddatasource
    driverclassname: com.mysql.jdbc.driver
    master:
      url: jdbc:mysql://localhost:3306/data_master
      username: root
      password: 123456
    slave:
      url: jdbc:mysql://localhost:3306/data_slave
      username: root
      password: 123456

从实际开发角度,这两个数据源需要配置主从复制流程,再基于安全角度,写库可以只给写权限,读库只给读权限。

map容器加载

@configuration
public class druidconfig {
    // 忽略参数加载,源码中有
    @bean
    @primary
    public datasource primarydatasource() {
        map<object, object> map = new hashmap<>();
        map.put("masterdatasource", masterdatasource());
        map.put("slavedatasource", slavedatasource());
        routedatasource routedatasource = new routedatasource();
        routedatasource.settargetdatasources(map);
        routedatasource.setdefaulttargetdatasource(masterdatasource());
        return routedatasource ;
    }
    private datasource masterdatasource() {
        return getdefdatasource(masterurl,masterusername,masterpassword);
    }
    private datasource slavedatasource() {
        return getdefdatasource(slaveurl,slaveusername,slavepassword);
    }
    private datasource getdefdatasource (string url,string username,string password){
        druiddatasource datasource = new druiddatasource();
        datasource.setdriverclassname(driverclassname);
        datasource.seturl(url);
        datasource.setusername(username);
        datasource.setpassword(password);
        return datasource;
    }
}

这里的map容器管理两个key,masterdatasource和slavedatasource代表两个不同的库,使用不同的key即加载对应的库。

2、容器key管理

使用threadlocal管理当前会会话中线程参数,存取使用极其方便。

public class routecontext implements autocloseable {

    private static final threadlocal<string> threadlocal = new threadlocal<>();
    public static void setroutekey (string key){
        threadlocal.set(key);
    }
    public static string getroutekey() {
        string key = threadlocal.get();
        return key == null ? "masterdatasource" : key;
    }
    @override
    public void close() {
        threadlocal.remove();
    }
}

3、路由key实现

获取threadlocal中,当前数据源的key,适配相关联的数据源。

public class routedatasource extends abstractroutingdatasource {
    @override
    protected object determinecurrentlookupkey() {
        return routecontext.getroutekey();
    }
}

三、读写分离

1、aop思维

基于aop的切面思想,不同的方法类型,去设置对应路由key,这样就可以在业务逻辑执行之前,切换到不同的数据源。

aspect
@component
@order(1)
public class readwriteaop {

    private static logger logger = loggerfactory.getlogger(readwriteaop.class) ;

    @before("execution(* com.master.slave.controller.*.*(..))")
    public void setreaddatasourcetype() {
        httpservletrequest request = ((servletrequestattributes) requestcontextholder.getrequestattributes()).getrequest();
        string method = request.getrequesturi() ;
        boolean rwflag = readorwrite(method) ;
        if (rwflag){
            routecontext.setroutekey("slavedatasource");
        } else {
            routecontext.setroutekey("masterdatasource");
        }
        logger.info("请求方法:"+method+";执行库:"+routecontext.getroutekey());
    }

    private string[] readarr = new string[]{"select","count","query","get","find"} ;
    private boolean readorwrite (string method){
        for (string readvar:readarr) {
            if (method.contains(readvar)){
                return true ;
            }
        }
        return false ;
    }
}

常见的读取方法:select、count、query、get、find等等,方法的命名要遵循自定义的路由规则。

2、提供测试接口

控制层api

import com.master.slave.entity.user;
import com.master.slave.service.userservice;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.restcontroller;
import javax.annotation.resource;

@restcontroller
public class usercontroller {

    @resource
    private userservice userservice ;

    @getmapping("/selectbyid")
    public user selectbyid (@requestparam("id") integer id) {
        return userservice.selectbyid(id) ;
    }

    @getmapping("/insert")
    public string insert () {
        user user = new user("张三","write") ;
        userservice.insert(user) ;
        return "success" ;
    }
}

服务实现

@service
public class userservice {

    @resource
    private usermapper usermapper ;

    public user selectbyid (integer id) {
        return usermapper.selectbyid(id) ;
    }

    public void insert (user user){
        usermapper.insert(user);
    }
}

这样数据源基于不同的类型方法就会一直的动态切换。

四、源代码地址

github·地址
https://github.com/cicadasmile/data-manage-parent
gitee·地址
https://gitee.com/cicadasmile/data-manage-parent

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网