Spring+MyBatis实现数据读写分离的实例代码
本文介绍了spring boot + mybatis读写分离,有需要了解spring+mybatis读写分离的朋友可参考。希望此文章对各位有所帮助。
其最终实现功能:
- 默认更新操作都使用写数据源
- 读操作都使用slave数据源
- 特殊设置:可以指定要使用的数据源类型及名称(如果有名称,则会根据名称使用相应的数据源)
其实现原理如下:
- 通过spring aop对dao层接口进行拦截,并对需要指定数据源的接口在thradlocal中设置其数据源类型及名称
- 通过mybatsi的插件,对根据更新或者查询操作在threadlocal中设置数据源(dao层没有指定的情况下)
- 继承abstractroutingdatasource类。
在此直接写死使用hikaricp作为数据源
其实现步骤如下:
- 定义其数据源配置文件并进行解析为数据源
- 定义abstractroutingdatasource类及其它注解
- 定义aop拦截
- 定义mybatis插件
- 整合在一起
1.配置及解析类
其配置参数直接使用hikaricp的配置,其具体参数可以参考hikaricp。
在此使用yaml
格式,名称为datasource.yaml
,内容如下:
dds: write: jdbcurl: jdbc:mysql://localhost:3306/order password: liu123 username: root maxpoolsize: 10 minidle: 3 poolname: master read: - jdbcurl: jdbc:mysql://localhost:3306/test password: liu123 username: root maxpoolsize: 10 minidle: 3 poolname: slave1 - jdbcurl: jdbc:mysql://localhost:3306/test2 password: liu123 username: root maxpoolsize: 10 minidle: 3 poolname: slave2
定义该配置所对应的bean,名称为dbconfig,内容如下:
@component @configurationproperties(locations = "classpath:datasource.yaml", prefix = "dds") public class dbconfig { private list<hikariconfig> read; private hikariconfig write; public list<hikariconfig> getread() { return read; } public void setread(list<hikariconfig> read) { this.read = read; } public hikariconfig getwrite() { return write; } public void setwrite(hikariconfig write) { this.write = write; } }
把配置转换为datasource的工具类,名称:datasourceutil
,内容如下:
import com.zaxxer.hikari.hikariconfig; import com.zaxxer.hikari.hikaridatasource; import javax.sql.datasource; import java.util.arraylist; import java.util.list; public class datasourceutil { public static datasource getdatasource(hikariconfig config) { return new hikaridatasource(config); } public static list<datasource> getdatasource(list<hikariconfig> configs) { list<datasource> result = null; if (configs != null && configs.size() > 0) { result = new arraylist<>(configs.size()); for (hikariconfig config : configs) { result.add(getdatasource(config)); } } else { result = new arraylist<>(0); } return result; } }
2.注解及动态数据源
定义注解@datasource,其用于需要对个别方法指定其要使用的数据源(如某个读操作需要在master上执行,但另一读方法b需要在读数据源的具体一台上面执行)
@retention(retentionpolicy.runtime) @target(elementtype.method) public @interface datasource { /** * 类型,代表是使用读还是写 * @return */ datasourcetype type() default datasourcetype.write; /** * 指定要使用的datasource的名称 * @return */ string name() default ""; }
定义数据源类型,分为两种:read,write,内容如下:
public enum datasourcetype { read, write; }
定义保存这此共享信息的类dynamicdatasourceholder
,在其中定义了两个threadlocal
和一个map,holder
用于保存当前线程的数据源类型(读或者写),pool
用于保存数据源名称(如果指定),其内容如下:
import java.util.map; import java.util.concurrent.concurrenthashmap; public class dynamicdatasourceholder { private static final map<string, datasourcetype> cache = new concurrenthashmap<>(); private static final threadlocal<datasourcetype> holder = new threadlocal<>(); private static final threadlocal<string> pool = new threadlocal<>(); public static void puttocache(string key, datasourcetype datasourcetype) { cache.put(key,datasourcetype); } public static datasourcetype getfromcach(string key) { return cache.get(key); } public static void putdatasource(datasourcetype datasourcetype) { holder.set(datasourcetype); } public static datasourcetype getdatasource() { return holder.get(); } public static void putpoolname(string name) { if (name != null && name.length() > 0) { pool.set(name); } } public static string getpoolname() { return pool.get(); } public static void cleardatasource() { holder.remove(); pool.remove(); } }
动态数据源类为dynamicdatasoruce
,其继承自abstractroutingdatasource
,可以根据返回的key切换到相应的数据源,其内容如下:
import com.zaxxer.hikari.hikaridatasource; import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; import javax.sql.datasource; import java.util.hashmap; import java.util.list; import java.util.map; import java.util.concurrent.concurrenthashmap; import java.util.concurrent.threadlocalrandom; public class dynamicdatasource extends abstractroutingdatasource { private datasource writedatasource; private list<datasource> readdatasource; private int readdatasourcesize; private map<string, string> datasourcemapping = new concurrenthashmap<>(); @override public void afterpropertiesset() { if (this.writedatasource == null) { throw new illegalargumentexception("property 'writedatasource' is required"); } setdefaulttargetdatasource(writedatasource); map<object, object> targetdatasource = new hashmap<>(); targetdatasource.put(datasourcetype.write.name(), writedatasource); string poolname = ((hikaridatasource)writedatasource).getpoolname(); if (poolname != null && poolname.length() > 0) { datasourcemapping.put(poolname,datasourcetype.write.name()); } if (this.readdatasource == null) { readdatasourcesize = 0; } else { for (int i = 0; i < readdatasource.size(); i++) { targetdatasource.put(datasourcetype.read.name() + i, readdatasource.get(i)); poolname = ((hikaridatasource)readdatasource.get(i)).getpoolname(); if (poolname != null && poolname.length() > 0) { datasourcemapping.put(poolname,datasourcetype.read.name() + i); } } readdatasourcesize = readdatasource.size(); } settargetdatasources(targetdatasource); super.afterpropertiesset(); } @override protected object determinecurrentlookupkey() { datasourcetype datasourcetype = dynamicdatasourceholder.getdatasource(); string datasourcename = null; if (datasourcetype == null ||datasourcetype == datasourcetype.write || readdatasourcesize == 0) { datasourcename = datasourcetype.write.name(); } else { string poolname = dynamicdatasourceholder.getpoolname(); if (poolname == null) { int idx = threadlocalrandom.current().nextint(0, readdatasourcesize); datasourcename = datasourcetype.read.name() + idx; } else { datasourcename = datasourcemapping.get(poolname); } } dynamicdatasourceholder.cleardatasource(); return datasourcename; } public void setwritedatasource(datasource writedatasource) { this.writedatasource = writedatasource; } public void setreaddatasource(list<datasource> readdatasource) { this.readdatasource = readdatasource; } }
3.aop拦截
如果在相应的dao层做了自定义配置(指定数据源),则在些处理。解析相应方法上的@datasource
注解,如果存在,并把相应的信息保存至上面的dynamicdatasourceholder
中。在此对com.hfjy.service.order.dao
包进行做拦截。内容如下:
import com.hfjy.service.order.anno.datasource; import com.hfjy.service.order.wr.dynamicdatasourceholder; import org.aspectj.lang.joinpoint; import org.aspectj.lang.annotation.after; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.aspectj.lang.annotation.pointcut; import org.aspectj.lang.reflect.methodsignature; import org.springframework.stereotype.component; import java.lang.reflect.method; /** * 使用aop拦截,对需要特殊方法可以指定要使用的数据源名称(对应为连接池名称) */ @aspect @component public class dynamicdatasourceaspect { @pointcut("execution(public * com.hfjy.service.order.dao.*.*(*))") public void dynamic(){} @before(value = "dynamic()") public void beforeopt(joinpoint point) { object target = point.gettarget(); string methodname = point.getsignature().getname(); class<?>[] clazz = target.getclass().getinterfaces(); class<?>[] parametertype = ((methodsignature)point.getsignature()).getmethod().getparametertypes(); try { method method = clazz[0].getmethod(methodname,parametertype); if (method != null && method.isannotationpresent(datasource.class)) { datasource datasource = method.getannotation(datasource.class); dynamicdatasourceholder.putdatasource(datasource.type()); string poolname = datasource.name(); dynamicdatasourceholder.putpoolname(poolname); dynamicdatasourceholder.puttocache(clazz[0].getname() + "." + methodname, datasource.type()); } } catch (exception e) { e.printstacktrace(); } } @after(value = "dynamic()") public void afteropt(joinpoint point) { dynamicdatasourceholder.cleardatasource(); } }
4.mybatis插件
如果在dao层没有指定相应的要使用的数据源,则在此进行拦截,根据是更新还是查询设置数据源的类型,内容如下:
import org.apache.ibatis.executor.executor; import org.apache.ibatis.mapping.mappedstatement; import org.apache.ibatis.mapping.sqlcommandtype; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.resulthandler; import org.apache.ibatis.session.rowbounds; import java.util.properties; @intercepts({ @signature(type = executor.class, method = "update", args = {mappedstatement.class, object.class}), @signature(type = executor.class, method = "query", args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class}) }) public class dynamicdatasourceplugin implements interceptor { @override public object intercept(invocation invocation) throws throwable { mappedstatement ms = (mappedstatement)invocation.getargs()[0]; datasourcetype datasourcetype = null; if ((datasourcetype = dynamicdatasourceholder.getfromcach(ms.getid())) == null) { if (ms.getsqlcommandtype().equals(sqlcommandtype.select)) { datasourcetype = datasourcetype.read; } else { datasourcetype = datasourcetype.write; } dynamicdatasourceholder.puttocache(ms.getid(), datasourcetype); } dynamicdatasourceholder.putdatasource(datasourcetype); return invocation.proceed(); } @override public object plugin(object target) { if (target instanceof executor) { return plugin.wrap(target, this); } else { return target; } } @override public void setproperties(properties properties) { } }
5.整合
在里面定义mybatis要使用的内容及datasource
,内容如下:
import com.hfjy.service.order.wr.dbconfig; import com.hfjy.service.order.wr.datasourceutil; import com.hfjy.service.order.wr.dynamicdatasource; import org.apache.ibatis.session.sqlsessionfactory; import org.mybatis.spring.sqlsessionfactorybean; import org.mybatis.spring.annotation.mapperscan; import org.springframework.beans.factory.annotation.qualifier; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.core.io.classpathresource; import org.springframework.core.io.support.pathmatchingresourcepatternresolver; import org.springframework.jdbc.datasource.datasourcetransactionmanager; import javax.annotation.resource; import javax.sql.datasource; @configuration @mapperscan(value = "com.hfjy.service.order.dao", sqlsessionfactoryref = "sqlsessionfactory") public class datasourceconfig { @resource private dbconfig dbconfig; @bean(name = "datasource") public dynamicdatasource datasource() { dynamicdatasource datasource = new dynamicdatasource(); datasource.setwritedatasource(datasourceutil.getdatasource(dbconfig.getwrite())); datasource.setreaddatasource(datasourceutil.getdatasource(dbconfig.getread())); return datasource; } @bean(name = "transactionmanager") public datasourcetransactionmanager datasourcetransactionmanager(@qualifier("datasource") datasource datasource) { return new datasourcetransactionmanager(datasource); } @bean(name = "sqlsessionfactory") public sqlsessionfactory sqlsessionfactory(@qualifier("datasource") datasource datasource) throws exception { sqlsessionfactorybean sessionfactorybean = new sqlsessionfactorybean(); sessionfactorybean.setconfiglocation(new classpathresource("mybatis-config.xml")); sessionfactorybean.setmapperlocations(new pathmatchingresourcepatternresolver() .getresources("classpath*:mapper/*.xml")); sessionfactorybean.setdatasource(datasource); return sessionfactorybean.getobject(); } }
如果不清楚,可以查看github上源码orderdemo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。