数据源管理 | 主从库动态路由,AOP模式读写分离
程序员文章站
2022-04-15 16:25:03
本文源码: "GitHub·点这里" || "GitEE·点这里" 一、多数据源应用 1、基础描述 在相对复杂的应用服务中,配置多个数据源是常见现象,例如常见的:配置主从数据库用来写数据,再配置一个从库读数据,这种读写分离模式可以缓解数据库压力,提高系统的并发能力和稳定性,执行效率。 2、核心API ......
本文源码: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