Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例
本文将介绍使用spring boot集成mybatis并实现主从库分离的实现(同样适用于多数据源)。延续之前的spring boot 集成mybatis。项目还将集成分页插件pagehelper、通用mapper以及druid。
新建一个maven项目,最终项目结构如下:
多数据源注入到sqlsessionfactory
pom增加如下依赖:
<!--json--> <dependency> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-core</artifactid> </dependency> <dependency> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-databind</artifactid> </dependency> <dependency> <groupid>com.fasterxml.jackson.datatype</groupid> <artifactid>jackson-datatype-joda</artifactid> </dependency> <dependency> <groupid>com.fasterxml.jackson.module</groupid> <artifactid>jackson-module-parameter-names</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-jdbc</artifactid> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>druid</artifactid> <version>1.0.11</version> </dependency> <!--mybatis--> <dependency> <groupid>org.mybatis.spring.boot</groupid> <artifactid>mybatis-spring-boot-starter</artifactid> <version>1.1.1</version> </dependency> <!--mapper--> <dependency> <groupid>tk.mybatis</groupid> <artifactid>mapper-spring-boot-starter</artifactid> <version>1.1.0</version> </dependency> <!--pagehelper--> <dependency> <groupid>com.github.pagehelper</groupid> <artifactid>pagehelper-spring-boot-starter</artifactid> <version>1.1.0</version> <exclusions> <exclusion> <artifactid>mybatis-spring-boot-starter</artifactid> <groupid>org.mybatis.spring.boot</groupid> </exclusion> </exclusions> </dependency>
这里需要注意的是:项目是通过扩展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration来实现多数据源注入的。在mybatis-spring-boot-starter:1.2.0中,该类取消了默认构造函数,因此本项目依旧使用1.1.0版本。需要关注后续版本是否会重新把扩展开放处理。
之所以依旧使用旧方案,是我个人认为开放扩展是合理的,相信在未来的版本中会回归。
如果你需要其他方案可参考
增加主从库配置(application.yml)
druid: type: com.alibaba.druid.pool.druiddatasource master: url: jdbc:mysql://192.168.249.128:3307/db-test?characterencoding=utf-8&autoreconnect=true&zerodatetimebehavior=converttonull&useunicode=true driver-class-name: com.mysql.jdbc.driver username: root password: root initial-size: 5 min-idle: 1 max-active: 100 test-on-borrow: true slave: url: jdbc:mysql://192.168.249.128:3317/db-test?characterencoding=utf-8&autoreconnect=true&zerodatetimebehavior=converttonull&useunicode=true&characterencoding=utf-8 driver-class-name: com.mysql.jdbc.driver username: root password: root initial-size: 5 min-idle: 1 max-active: 100 test-on-borrow: true
创建数据源
@configuration @enabletransactionmanagement public class datasourceconfiguration { @value("${druid.type}") private class<? extends datasource> datasourcetype; @bean(name = "masterdatasource") @primary @configurationproperties(prefix = "druid.master") public datasource masterdatasource(){ return datasourcebuilder.create().type(datasourcetype).build(); } @bean(name = "slavedatasource") @configurationproperties(prefix = "druid.slave") public datasource slavedatasource1(){ return datasourcebuilder.create().type(datasourcetype).build(); } }
将多数据源注入到sqlsessionfactory中
前面提到了这里通过扩展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration来实现多数据源注入的
@configuration @autoconfigureafter({datasourceconfiguration.class}) public class mybatisconfiguration extends mybatisautoconfiguration { private static log logger = logfactory.getlog(mybatisconfiguration.class); @resource(name = "masterdatasource") private datasource masterdatasource; @resource(name = "slavedatasource") private datasource slavedatasource; @bean public sqlsessionfactory sqlsessionfactory() throws exception { return super.sqlsessionfactory(roundrobindatasouceproxy()); } public abstractroutingdatasource roundrobindatasouceproxy(){ readwritesplitroutingdatasource proxy = new readwritesplitroutingdatasource(); map<object,object> targetdataresources = new classloaderrepository.softhashmap(); targetdataresources.put(dbcontextholder.dbtype.master,masterdatasource); targetdataresources.put(dbcontextholder.dbtype.slave,slavedatasource); proxy.setdefaulttargetdatasource(masterdatasource);//默认源 proxy.settargetdatasources(targetdataresources); return proxy; } }
实现读写分离(多数据源分离)
这里主要思路如下:
1-将不同的数据源标识记录在threadlocal中
2-通过注解标识出当前的service方法使用哪个库
3-通过spring aop实现拦截注解并注入不同的标识到threadlocal中
4-获取源的时候通过threadlocal中不同的标识给出不同的sqlsession
标识存放threadlocal的实现
public class dbcontextholder { public enum dbtype{ master,slave } private static final threadlocal<dbtype> contextholder = new threadlocal<>(); public static void setdbtype(dbtype dbtype){ if(dbtype==null)throw new nullpointerexception(); contextholder.set(dbtype); } public static dbtype getdbtype(){ return contextholder.get()==null?dbtype.master:contextholder.get(); } public static void cleardbtype(){ contextholder.remove(); } }
注解实现
/** * 该注解注释在service方法上,标注为链接slaves库 * created by jason on 2017/3/6. */ @target({elementtype.method,elementtype.type}) @retention(retentionpolicy.runtime) public @interface readonlyconnection { }
spring aop对注解的拦截
@aspect @component public class readonlyconnectioninterceptor implements ordered { public static final logger logger = loggerfactory.getlogger(readonlyconnectioninterceptor.class); @around("@annotation(readonlyconnection)") public object proceed(proceedingjoinpoint proceedingjoinpoint,readonlyconnection readonlyconnection) throws throwable { try { logger.info("set database connection to read only"); dbcontextholder.setdbtype(dbcontextholder.dbtype.slave); object result = proceedingjoinpoint.proceed(); return result; }finally { dbcontextholder.cleardbtype(); logger.info("restore database connection"); } } @override public int getorder() { return 0; } }
根据标识获取不同源
这里我们通过扩展abstractroutingdatasource来获取不同的源。它是spring提供的一个可以根据用户发起的不同请求去转换不同的数据源,比如根据用户的不同地区语言选择不同的数据库。通过查看源码可以发现,它是通过determinecurrentlookupkey()返回的不同key到sqlsessionfactory中获取不同源(前面已经展示了如何在sqlsessionfactory中注入多个源)
public class readwritesplitroutingdatasource extends abstractroutingdatasource { @override protected object determinecurrentlookupkey() { return dbcontextholder.getdbtype(); } }
以上就完成了读写分离(多数据源)的配置方案。下面是一个具体的实例
使用方式
entity
@table(name = "t_sys_dic_type") public class dictype extends baseentity{ string code; string name; integer status; ... }
mapper
public interface dictypemapper extends basemapper<dictype> { }
service
@service public class dictypeservice { @autowired private dictypemapper dictypemapper; @readonlyconnection public list<dictype> getall(dictype dictype){ if (dictype.getpage() != null && dictype.getrows() != null) { pagehelper.startpage(dictype.getpage(), dictype.getrows()); } return dictypemapper.selectall(); } }
注意这里的@readonlyconnection注解
controller
@restcontroller @requestmapping("/dictype") public class dictypecontroller { @autowired private dictypeservice dictypeservice; @requestmapping(value = "/all") public pageinfo<dictype> getall(dictype dictype){ list<dictype> dictypelist = dictypeservice.getall(dictype); return new pageinfo<>(dictypelist); } }
通过mvn spring-boot:run启动后,即可通过http://localhost:9090/dictype/all 获取到数据
后台打印出
c.a.d.m.readonlyconnectioninterceptor : set database connection to read only
说明使用了从库的链接获取数据
备注:如何保证多源事务呢?
1-在读写分离场景中不会考虑主从库事务,在纯读的上下文上使用@readonlyconnection标签。其他则默认使用主库。
2-在多源场景中,spring的@transaction是可以保证多源的事务性的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 引用母版页后在page页面修改母版页控件的值的方法
下一篇: 简介Java程序的Shell脚本包装