spring 配置多数据源实现数据库读写分离
    

现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个master数据库,多个slave数据库。master库负责数据更新和实时数据查询,slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的cpu较多,从而影响用户体验。我们通常的做法就是把查询从主库中抽取出来,采用多个从库,使用负载均衡,减轻每个从库的查询压力。

采用读写分离技术的目标:有效减轻master库的压力,又可以把用户查询数据的请求分发到不同的slave库,从而保证系统的健壮性。我们看下采用读写分离的背景。

随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力也就越来越大,采用传统的方式,比如:数据库或者sql的优化基本已达不到要求,这个时候可以采用读写分离的策 略来改变现状。

具体到开发中,如何方便的实现读写分离呢?目前常用的有两种方式:

1 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是masterdatasource,另一个是slavedatasource。更新数据时我们读取masterdatasource,查询数据时我们读取slavedatasource。这种方式很简单,我就不赘述了。

2 第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:annotation,spring aop ,反射。下面会详细的介绍实现方式。

在介绍实现方式之前,我们先准备一些必要的知识,spring 的abstractroutingdatasource类

abstractroutingdatasource这个类 是spring2.0以后增加的,我们先来看下abstractroutingdatasource的定义:

public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean {}

public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean {

private maptargetdatasources;

private object defaulttargetdatasource;

private datasourcelookup datasourcelookup = new jndidatasourcelookup();

private mapresolveddatasources;

private datasource resolveddefaultdatasource;

abstractroutingdatasource继承了abstractdatasource ,而abstractdatasource 又是datasource 的子类。

datasource ? 是javax.sql 的数据源接口,定义如下:

public interface datasource extends commondatasource,wrapper {

connection getconnection() throws sqlexception;

connection getconnection(string username, string password)

throws sqlexception;

}

datasource 接口定义了2个方法,都是获取数据库连接。我们在看下abstractroutingdatasource 如何实现了datasource接口:

public connection getconnection() throws sqlexception {

return determinetargetdatasource().getconnection();

}

public connection getconnection(string username, string password) throws sqlexception {

return determinetargetdatasource().getconnection(username, password);

}

很显然就是调用自己的determinetargetdatasource() ?方法获取到connection。determinetargetdatasource方法定义如下:

protected datasource determinetargetdatasource() {

assert.notnull(this.resolveddatasources, "datasource router not initialized");

object lookupkey = determinecurrentlookupkey();

datasource datasource = this.resolveddatasources.get(lookupkey);

if (datasource == null && (this.lenientfallback || lookupkey == null)) {

datasource = this.resolveddefaultdatasource;

}

if (datasource == null) {

throw new illegalstateexception("cannot determine target datasource for lookup key [" + lookupkey + "]");

}

return datasource;

}

我们最关心的还是下面2句话:

object lookupkey = determinecurrentlookupkey();

datasource datasource = this.resolveddatasources.get(lookupkey);

determinecurrentlookupkey方法返回lookupkey,resolveddatasources方法就是根据lookupkey从map中获得数据源。resolveddatasources 和determinecurrentlookupkey定义如下:

private map; resolveddatasources;

protected abstract object determinecurrentlookupkey()

看到以上定义,我们是不是有点思路了,resolveddatasources是map类型,我们可以把masterdatasource和slavedatasource存到map中,如下:

key value

master ? ? ? ? masterdatasource

slave ? ? ? ? ? ? ?slavedatasource

我们在写一个类dynamicdatasource ?继承abstractroutingdatasource,实现其determinecurrentlookupkey() 方法,该方法返回map的key,master或slave。

好了,说了这么多,有点烦了,下面我们看下怎么实现。

上面已经提到了我们要使用的技术,我们先看下annotation的定义:

@retention(retentionpolicy.runtime)

@target(elementtype.method)

public @interface datasource {

string value();

}

我们还需要实现spring的抽象类abstractroutingdatasource,就是实现determinecurrentlookupkey方法:

public class dynamicdatasource extends abstractroutingdatasource {

@override

protected object determinecurrentlookupkey() {

// todo auto-generated method stub

return dynamicdatasourceholder.getdatasouce();

}

}

public class dynamicdatasourceholder {

public static final threadlocalholder = new threadlocal();

public static void putdatasource(string name) {

holder.set(name);

}

public static string getdatasouce() {

return holder.get();

}

}

从dynamicdatasource 的定义看出,他返回的是dynamicdatasourceholder.getdatasouce()值,我们需要在程序运行时调用dynamicdatasourceholder.putdatasource()方法,对其赋值。下面是我们实现的核心部分,也就是aop部分,datasourceaspect定义如下:

public class datasourceaspect {

public void before(joinpoint point)

{

object target = point.gettarget();

string method = point.getsignature().getname();

class[] classz = target.getclass().getinterfaces();

class[] parametertypes = ((methodsignature) point.getsignature())

.getmethod().getparametertypes();

try {

method m = classz[0].getmethod(method, parametertypes);

if (m != null && m.isannotationpresent(datasource.class)) {

datasource data = m

.getannotation(datasource.class);

dynamicdatasourceholder.putdatasource(data.value());

system.out.println(data.value());

}

} catch (exception e) {

// todo: handle exception

}

}

}

为了方便测试,我定义了2个数据库,shop模拟master库,test模拟slave库,shop和test的表结构一致,但数据不同,数据库配置如下:

class="org.springframework.jdbc.datasource.drivermanagerdatasource">

class="org.springframework.jdbc.datasource.drivermanagerdatasource">

class="org.springframework.jdbc.datasource.datasourcetransactionmanager">

在spring的配置中增加aop配置

下面是mybatis的usermapper的定义,为了方便测试,登录读取的是master库,用户列表读取slave库:

public interface usermapper {

@datasource("master")

public void add(user user);

@datasource("master")

public void update(user user);

@datasource("master")

public void delete(int id);

@datasource("slave")

public user loadbyid(int id);

@datasource("master")

public user loadbyname(string name);

@datasource("slave")

public listlist();

}

好了,运行我们的eclipse看看效果,输入用户名admin 登录看看效果

从图中可以看出,登录的用户和用户列表的数据是不同的,也验证了我们的实现,登录读取master库,用户列表读取slave库。

例子来源:

http://www.cnblogs.com/surge/p/3582248.html

二、配置动态数据源

java:/datasources/visesbdb

com.myproject.bean

true

utf-8

${hibernate.dialect}

${hibernate.hbm2ddl.auto}

${hibernate.show_sql}

${hibernate.format_sql}

${hibernate.cache.use_second_level_cache}

${hibernate.cache.use_query_cache}

/**

* 在applicationcontext中配置本地数据源作为默认数据源

* 读取project-datasource-jndi.properties中的jndi名称获取其他节点的数据源

* 该文件放在d:\jboss-5.1.0.ga\server\default\conf\props 目录下

*

*/

public class mutidatasourcebean extends abstractroutingdatasource implements applicationcontextaware {

private static final logger logger = loggerfactory.getlogger(mutidatasourcebean.class);

private static applicationcontext ctx;

private maptds = new hashmap();

@override

public void setapplicationcontext(applicationcontext applicationcontext)

throws beansexception {

ctx = applicationcontext;

}

@override

protected object determinecurrentlookupkey() {

return datasourcecontextholder.getdatasourcetype();

}

//重写initializingbean类中方法

@override

public void afterpropertiesset() {

logger.info("init mutidatasource start...");

try {

initailizemutidatasource();

} catch (exception e) {

logger.error("init mutidatasource error...", e);

}

logger.info("init mutidatasource end...");

super.afterpropertiesset();

}

/**

* 读取配置文件中的jndi名称,获取数据源

* @throws exception

*/

private void initailizemutidatasource() throws exception {

// 读取数据源配置文件

resourcebundle lw = resourcebundle.getbundle("props.project-datasource-jndi");

// 初始化jndi context

context jndictx = new initialcontext();

defaultlistablebeanfactory dlbf = (defaultlistablebeanfactory) ctx.getautowirecapablebeanfactory();

// 获取配置的数据源

for(string key : lw.keyset()){

object ds = jndictx.lookup(lw.getstring(key));

// 将数据源交给spring管理

dlbf.registersingleton(key, ds);

tds.put(key, ds);

}

super.settargetdatasources(tds);

}

@override

public void settargetdatasources(maptargetdatasources) {

tds = targetdatasources;

super.settargetdatasources(targetdatasources);

}

}

/**

* 通过threadlocal来存储当前所使用数据源对应的key

*

*/

public class datasourcecontextholder {

private static final threadlocalcontextholder = new threadlocal();

public static void setdatasourcetype(string datasourcetype) {

contextholder.set(datasourcetype);

}

public static string getdatasourcetype() {

return contextholder.get();

}

public static void cleardatasourcetype() {

contextholder.remove();

}

}

查询前设置值:

mutidatasourceutil.determinetargetdatasourcebyinstanceuuid(esbserviceinstancev.getinstanceuuid());

mapresult = esbservicemonitordao.findesbserviceinstancevpagedlist(pagequeryparameter, esbserviceinstancev);

// reset datasource

datasourcecontextholder.cleardatasourcetype();

public class mutidatasourceutil {

/**

* 通过实例uuid切换到对应的数据源

*

* @param instanceuuid

*/

public static void determinetargetdatasourcebyinstanceuuid(string instanceuuid) {

if(stringutils.isnotblank(instanceuuid) && stringutils.contains(instanceuuid, '-')){

datasourcecontextholder.setdatasourcetype(stringutils.substringbefore(instanceuuid, "-"));

}

}

}

lightesb-datasource-jndi.properties:

n1=java:/datasources/visesbdb

n2=java:/datasources/n2visesbdb

实例号如:

n1-ab2dfe3c48ba43d699529868b20152cc

相关阅读
  • MySQL主从同步、读写分离配置步骤
  • Oracle 数据库性能优化分析与配置
  • 紫金桥实时数据库点组态协同配置
  • 工控系统中实时数据库关键技术研究
  • 工控系统中实时数据库关键技术研究
  • 一种实现对RFID移动读写器自动配置
  • 工控系统中实时数据库关键技术研究
  • 配电管理信息系统数据库的设计与实
  • 一种云数据库的设计与实现
  • 基于C/S和B/S结构的多数据源电能质
  • 紫金桥实时数据库平台实现灌区信息
  • 紫金桥实时数据库平台实现灌区信息
  • 紫金桥实时数据库平台实现灌区信息
  • 关于PCBN刀具多媒体数据库咨询管理
  • 华丹Charisma在线自定义Web报表的1
  • 基于数据库的远程测控自适应命令链
  • PCBN刀具多媒体数据库咨询管理系统
  • 语音信号识别基于盲源信号分离的实
  • 高频读写器的实现原理
  • 如何实现用户配置文件漫游
  •  



     
     
         

    收录时间:2016年11月12日 15:51:22 来源:未知 作者:匿名
    上一篇:mysql中text,longtext,mediumtext字段类型的意思,以及区别  (电脑版  手机版)
     
    创建分享人
    庄子往见之
    最新问题
     
    喜欢此文章的还喜欢
    Copyright by www.chinabaike.com;All rights reserved. 联系:QQ:469681782