采用读写分离技术的目标:有效减轻master库的压力,又可以把用户查询数据的请求分发到不同的slave库,从而保证系统的健壮性。我们看下采用读写分离的背景。
随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力也就越来越大,采用传统的方式,比如:数据库或者sql的优化基本已达不到要求,这个时候可以采用读写分离的策 略来改变现状。
具体到开发中,如何方便的实现读写分离呢?目前常用的有两种方式:
1 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是masterdatasource,另一个是slavedatasource。更新数据时我们读取masterdatasource,查询数据时我们读取slavedatasource。这种方式很简单,我就不赘述了。
,反射。下面会详细的介绍实现方式。
在介绍实现方式之前,我们先准备一些必要的知识,spring 的abstractroutingdatasource 类
abstractroutingdatasource这个类 是spring2.0以后增加的,我们先来看下abstractroutingdatasource的定义:
public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean {}
abstractroutingdatasource继承了abstractdatasource ,而abstractdatasource 又是datasource
的子类。datasource 是javax.sql 的数据源接口,定义如下:
datasource 接口定义了2个方法,都是获取数据库连接。我们在看下abstractroutingdatasource 如何实现了datasource接口:
很显然就是调用自己的determinetargetdatasource() 方法获取到connection。determinetargetdatasource方法定义如下:
我们最关心的还是下面2句话:
object lookupkey = determinecurrentlookupkey();
datasource datasource = this.resolveddatasources.get(lookupkey);
determinecurrentlookupkey方法返回lookupkey,resolveddatasources方法就是根据lookupkey从map中获得数据源。resolveddatasources 和determinecurrentlookupkey定义如下:
private map<object, datasource> resolveddatasources;
protected abstract object determinecurrentlookupkey()
看到以上定义,我们是不是有点思路了,resolveddatasources是map类型,我们可以把masterdatasource和slavedatasource存到map中,如下:
key value
master masterdatasource
slave slavedatasource
我们在写一个类dynamicdatasource 继承abstractroutingdatasource,实现其determinecurrentlookupkey() 方法,该方法返回map的key,master或slave。
好了,说了这么多,有点烦了,下面我们看下怎么实现。
上面已经提到了我们要使用的技术,我们先看下annotation的定义:
我们还需要实现spring的抽象类abstractroutingdatasource,就是实现determinecurrentlookupkey方法:
从dynamicdatasource 的定义看出,他返回的是dynamicdatasourceholder.getdatasouce()值,我们需要在程序运行时调用dynamicdatasourceholder.putdatasource()方法,对其赋值。下面是我们实现的核心部分,也就是aop部分,datasourceaspect定义如下:
为了方便测试,我定义了2个数据库,shop模拟master库,test模拟slave库,shop和test的表结构一致,但数据不同,数据库配置如下:
在spring的配置中增加aop配置
下面是mybatis的usermapper的定义,为了方便测试,登录读取的是master库,用户列表读取slave库:
好了,运行我们的eclipse看看效果,输入用户名admin 登录看看效果
从图中可以看出,登录的用户和用户列表的数据是不同的,也验证了我们的实现,登录读取master库,用户列表读取slave库。
首先说明,本文的配置使用的最直接的方式,实际用起来可能会很麻烦。
实际应用中可能存在多种结合的情况,你可以理解本文的含义,不要死板的使用。
针对这种情况,一个更好的解决方法可以参考(本人没有实际尝试过):
<a target="_blank" href="http://blog.csdn.net/lixiucheng005/article/details/17391857">http://blog.csdn.net/lixiucheng005/article/details/17391857</a>
还有一个通过spring<code>abstractroutingdatasource</code>路由接口的方式:
<a target="_blank" href="http://blog.csdn.net/xtj332/article/details/43953699">http://blog.csdn.net/xtj332/article/details/43953699</a>
这种情况可以参考本文,但是需要注意每一个数据库对应的mapper要在不同的包下方便区分和配置。
另外分库的情况下也会存在主从的情况,如果你的数据库从库过多,就参考上面提供的方法,或者寻找其他方式解决。
分库的情况下,不同的数据库的mapper一定放在不同的包下。
主从的情况下,同一个mapper会同时存在读写的情况,创建两个并不合适,使用同一个即可。但是这种情况下需要注意,spring对mapper自动生成的名字是相同的,而且类型也相同,这是就不能直接注入<code>mapper</code>接口。需要通过<code>sqlsession</code>来解决。
这个文件,主要是引入了<code>spring-datasource-master.xml</code>和<code>spring-datasource-slave.xml</code>。
和<code>master</code>区别不大,主要是<code>id</code>名字和数据源配置有区别。
这里需要注意<code><tx:method name="*" read-only="true"/></code>是只读的。如果不是从库,可以按主库进行配置。
在下面代码中:
必须通过<code>sqlsessionfactorybeanname</code>来指定不同的<code>sqlsessionfactory</code>。
这里是针对主从的情况进行设置的,两个配置扫描的<code>mapper</code>是一样的,所以没法直接注入,需要通过下面的麻烦方式注入。
因为<code>sqlsession</code>能通过<code>name</code>区分开,所以这里从<code>sqlsession</code>获取<code>mapper</code>。
另外如果需要考虑在同一个事务中写读的时候,需要使用相同的<code>writemapper</code>,这样在读的时候,才能获取事务中的最新数据。
以上是主从的情况。
在分库的情况时,由于不同mapper在不同的包下,所以可以直接使用<code>@resource</code>或者<code>@autowired</code>注入<code>mapper</code>,不需要通过<code>sqlsession</code>获取。