SpringBoot 读写分离实现(AbstractRoutingDataSource)


读写分离一直都是项目的标配,之前项目的做法非常简单,直接配置两个数据源,一个只读,一个只写,只读的放到xxx.read,只写的放到xxx.write包下。Service层调用的时候根据操作选择对应的数据源。主要配置:```略...classpath*:xxx/write/resource/*.sql.xml```以上实现了为只写的数据源配置事务。Mybatis自动扫描对应包下的xml文件。这样做优点还是很明显的,简单易懂。以后只要有新功能按照读写分离原则放到指定包下即可。缺点就是在Service层涉及到读写同时进行的时候,需要调用对应的Mapper,比如:xxxReadMapper,xxxWriteMapper 的方法。如果以后读写分离改成的数据库层处理,那么这里的代码就需要合并到一起,增加工作量。那有没有更好的方法呢?是否可以做到自动读写分离呢?当然是有的,而且还有很多种方式,比如通过数据库代理的方式,而不是通过代码来实现。或者还有其他开源框架。这里介绍下我的实现方式,基于AbstractRoutingDataSource。该类通过代理的方式实现了数据源的动态分配,在使用时通过自定义的key来选择对应的数据源。它的注释是这么说明的:```Abstract javax.sql.DataSource implementation that routes getConnection() calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context.```步骤1:执行db目录下的springboot.sql文件来初始化db,这里需要配置两个db,一个只读(springboot_r)一个写(springboot)。步骤2:继承自AbstractRoutingDataSource,初始化结束时自动扫描容器内的数据源,实现自动代理```@Component("dynamicDataSource")@Primary@ConfigurationProperties(prefix = "dynamicDatasource")public static class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware {public static final Map DATASOURCE_STRATEGY = new HashMap<>();private Map strategy = new HashMap<>();private ApplicationContext applicationContext;private String defaultDataSource;@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceHolder.getDataSource();}@Overrideprotected Object resolveSpecifiedLookupKey(Object lookupKey) {return super.resolveSpecifiedLookupKey(lookupKey);}@Overridepublic void afterPropertiesSet() {Map dataSources = applicationContext.getBeansOfType(DataSource.class);if (dataSources.size() == 0) {throw new IllegalStateException("Datasource can not found!!!");}// exclude current datasourceMap targetDataSource = excludeCurrentDataSource(dataSources);setTargetDataSources(targetDataSource);// 多数据源方法设置Iterator it = strategy.keySet().iterator();while (it.hasNext()) {String key = it.next();String[] values = strategy.get(key).split(",");for (String v : values) {if (StringUtils.isNotBlank(v)) {DATASOURCE_STRATEGY.put(v, key);}}}// 默认数据源设置setDefaultTargetDataSource(targetDataSource.get(getDefaultDataSource()));super.afterPropertiesSet();}/**** exclude current Datasource** @param dataSources* @return*/private Map excludeCurrentDataSource(Map dataSources) {Map targetDataSource = new HashMap<>();Iterator keys = dataSources.keySet().iterator();while (keys.hasNext()) {String key = keys.next();if (!(dataSources.get(key) instanceof DynamicDataSource)) {targetDataSource.put(key, dataSources.get(key));}}return targetDataSource;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public Map getStrategy() {return strategy;}public void setStrategy(Map strategy) {this.strategy = strategy;}public String getDefaultDataSource() {return defaultDataSource;}public void setDefaultDataSource(String defaultDataSource) {this.defaultDataSource = defaultDataSource;}}```步骤3:配置读和写的数据源```@ConfigurationProperties(prefix = "db.mybatis.jdbc")@Bean(destroyMethod = "close", name = "write")public DataSource dataSourceWrite() {log.info("*************************dataSource***********************");BasicDataSource dataSource = new BasicDataSource();dataSource.setRemoveAbandoned(true);dataSource.setTestWhileIdle(true);dataSource.setTimeBetweenEvictionRunsMillis(30000);dataSource.setNumTestsPerEvictionRun(30);dataSource.setMinEvictableIdleTimeMillis(1800000);return dataSource;}@ConfigurationProperties(prefix = "db.mybatis2.jdbc")@Bean(destroyMethod = "close", name = "read")public DataSource dataSourceRead() {log.info("*************************dataSource***********************");BasicDataSource dataSource = new BasicDataSource();dataSource.setRemoveAbandoned(true);dataSource.setTestWhileIdle(true);dataSource.setTimeBetweenEvictionRunsMillis(30000);dataSource.setNumTestsPerEvictionRun(30);dataSource.setMinEvictableIdleTimeMillis(1800000);return dataSource;}```步骤4:为动态数据源配置读写分离策略,这里使用的是最简单的前缀规则,如果有需要可以自行改成正则表达式的方式,以下配置定义了get,find,select开头的方法都使用read数据源```dynamicDatasource.strategy.read=get,find,selectdynamicDatasource.strategy.write=insert,update,delete,logindynamicDatasource.defaultDataSource=write```步骤5:单元测试,在test包下DynamicDataSourceTest类中有两个方法,一个测试只读一个测试写:```@Testpublic void testLogin() throws Exception {User user = new User();user.setUsername("11111111111");user.setPassword("123456");User loginUser = userService.login(user);System.out.println("登录结果:" + loginUser);}@Testpublic void testFindUser() throws Exception {User loginUser = userService.findUserByToken("xxx");System.out.println("查询用户结果:" + loginUser);}```执行testLogin单元测试可以看出这里的操作用的是写的数据源```ooo Using Connection [jdbc:mysql://localhost/springboot?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull, UserName=root@localhost, MySQL Connector Java]```执行testFindUser可以看出这里用的是读的数据源```ooo Using Connection [jdbc:mysql://localhost/springboot_r?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull, UserName=root@localhost, MySQL Connector Java]```这种方式优点:不需要再像之前一样对读写操作分离了,都可以统一到一个Mapper上,代码可以统一到一个包下。程序员甚至都不需要意识到数据库的读写分离。以后替换成db层处理也是非常方便的。注意点:因为事务和动态数据源切换都是基于AOP的,所以顺序非常重要。动态切换要在事务之前,如果发现无法动态切换数据源那么可以看下他们之间的顺序。以上代码已提交至SpringBootLearning的DynamicDataSource工程。----------说明:com.cml.springboot.framework.db 动态数据源配置包com.cml.springboot.framework.mybatis mybatis配置包,配置了mybatis规则和读写数据源**SpringBootLearning是对springboot学习与研究项目,是根据实际项目的形式对进行配置与处理,欢迎star与fork。**[oschina 地址]http://git.oschina.net/cmlbeliever/SpringBootLearning[github 地址]https://github.com/cmlbeliever/SpringBootLearning

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容