您的位置:首页 > 数据库 > MySQL

mysql读写分离之代码分析与设计(2)

2015-12-14 17:43 726 查看
ps:本文是对读写分离代码的分析与设计,如果想直接看代码实现,请越过本章,直接看第三章:读写分离之代码实现

读写分离的代码设计分两部分实现:

1.数据源以key-value形式存储,通过key来使用DataSource;

2.设计在哪些情况下需要切换数据源,需要切换到哪个key。

下面是详细分析:

 

一、 通过key来使用DataSource

AbstractRountingDataSource这个类是实现

字面的意思是“一个抽象类,用来选择数据源的途径”。而这个类的用途也正如其名,它可以实现数据源的切换。

下面我们通过源代码来分析一下,这个类是怎么实现切换数据源的。

package org.springframework.jdbc.datasource.lookup;//所在位置

 

import java.sql.Connection;

import java.sql.SQLExcetipon;

import java.util.HashMap;

import javax.sql.DataSource;

 

import org.springframework.beans.factory.InitializingBean;

import org.springframework.jdbc.datasource.AbstractDataSource;

import org.springframework.util.Assert;

 

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

/**

  * 所有备选的数据源

  * key-value(简称-数据源的真实名称)

  */

private Map<Object,Object> targetDataSource;

 

/**

  * 默认当前数据源

  */

private Object defaultTargetDataSource;

 

/**

  * 字面意思是宽容回退

    * 意思是在通过key来切换数据源如果没有找到,或者key本身就是null这个时候是否要使用

  * 默认数据源,true:使用默认数据源;false:抛出异常Cannot determine target DataSource * for lookup key lookupKey

  * 默认值:true ,表示使用宽容回退

  * 以上分析是从determineTargetDataSource()方法得出的 

  */

private boolean lenientFallback = true;

 

/**

  * JNDI通过key来查找数据源

  * 接口:DataSourceLookup 包含一个方法

  * DataSource getDataSource(String dataSourceName) throws     

  * DataSourceLookupFailureException

  */

private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

/**

  * 所有备选的数据库

  * key-value(简称-DataSource对象)

  */

private Map resolvedDataSources;

 

/**

  * 默认的数据源

  */

private DataSource resolvedDefaultDataSource;

 

/**

  * 提供了set方法,就可以使用spring的IOC了

  */

public void setTargetDataSources(Map<Object,Object> targetDataSource){

this.targetDataSources = targetDataSources;

}

 

/**

  * 提供了set方法,就可以使用spring的IOC了

  */

public void setDefaultTargetDataSource(Object defaultTargetDataSource){

this.defaultTargetDataSource = defaultTargetDataSource;

}

 

/**

  * 可以修改“宽容回退”的默认值

  */

public void setLenientFallback(boolean lenientFallback){

this.lenientFallback = lenientFallback;

}

 

/**

  * set方法

  */

public setDataSourceLookup(DataSourceLookup dataSourceLookup){

this.dataSourceLookup = (dataSourceLookup !=null?dataSourceLookup:new JndiDataSourceLookup());

}

 

/**

  * 通过targetDataSources(简称-数据源真实名称)

  * 通过数据源真实名称,查询DataSource对象

  * 简称-DataSource对象以key-value形式保存到targetDataSource集合中

  */

public void afterPropertiesSet(){

if(this.targetDataSources==null){

throws new IllegalArgumentException(“Property ‘targetDataSources’ is required”);

}

this.resolvedDataSources = new HashMap<Object,DataSource>(this.targetDataSources.size());

for(Map.Entry entry:this.targetDataSources.entrySet){

Object lookupkey = resolveSpecifiedLookupKey(entry.getKey());

DataSource dataSource = resolveSpecifiedLookupKey(entry.getKey());

DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());

this.resolvedDataSources.put(lookupKey,dataSource);

}

if(this.defaultTargetDataSource!=null){

this.resolvedDefaultDataSource=resolveSpecifiedDataSource(this.defaultTargetDataSource);

}

}

 

/**

  * 通过数据源真实名称,查询DataSource对象

  */

protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException{

if(dataSource instanceof DataSource){

return (DataSource)dataSource;

}

else if(dataSource instanceof String){

return this.dataSourceLookup.getDataSource((String)dataSource);

}

else{

throw new IllegalArgumentException(

“Illegal data source value = only [javax.sql.DataSource] and String supported: ”+dataSource);

}

}

 

/**

  * DataSource接口的实现

  */

public Connection getConnection() throws SQLException{

return determineTargetDataSource().getConnection();

}

public Connection getConnection(String username,String password) throws SQLException{

return determineTargetDataSource().getConnection(username,password);

}

 

/**

  * 通过简称查询DataSource对象

  */

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;

}

 

protected Object resolveSpecifiedLookupKey(Object lookupKey){

return lookupKey;

}

 

/**

  * 这个抽象方法需要重写,提供要查询的DataSource的key(简称)

  */

protected abstract Object determineCurrentLookupKey();

}

以上就是org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource的源代码(注释是根据我的理解加上的)。

分析一下关系:

abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

父类: AbstractDataSource

public abstract class AbstractDataSource implements DataSource

父类AbstractDataSource实现javax.sql.DataSource,说明AbstractRoutingDataSource也实现了javax.sql.DataSource接口;并且父类是个抽象类,它并没有实现javax.sql.DataSource的两个接口getConnection()、getConnection(String username,String password),所以在AbstractRou-

tingDataSource中实现了这两个接口(当然它也是Abstract类,也可以选择不实现这两个接口,但那样就不符合设计思路了)。

这里我要表达的意思是:这个AbstractRoutingDataSource“就是”(这么说不是很准确,但很容易理解)DataSource,只要两个Connection()接口能起到连接数据库的作用就OK了。

那么我们接着分析getConnection()这个方法实现(有参方法原理一样,这里为了减少冗余就只分析无参方法了)。

public Connection getConnection() throws SQLException{

return determineTargetDataSource().getConnection(); //相当于dataSource.getConnection();

}

 

/**

  * 通过简称查询DataSource对象

  */

protected DataSource determineTargetDataSource(){

Assert.notNull(this.resolvedDataSources,”DataSource router not initialized”); //如果resolvedDataSources为空就报错

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;

}

determineTargetDataSource()方法会让我们产生几个疑问:

疑问1:determineTargetDataSource中this.resolvedDataSources是什么时候赋值的呢?

答案:是在下面这个方法赋值的:

/**

  * 通过targetDataSources(简称-数据源真实名称)

  * 通过数据源真实名称,查询DataSource对象

  * 简称-DataSource对象以key-value形式保存到targetDataSource集合中

  */

public void afterPropertiesSet(){

if(this.targetDataSources==null){

throws new IllegalArgumentException(“Property ‘targetDataSources’ is required”);

}

this.resolvedDataSources = new HashMap<Object,DataSource>(this.targetDataSources.size());

for(Map.Entry entry:this.targetDataSources.entrySet){

Object lookupkey = resolveSpecifiedLookupKey(entry.getKey());

DataSource dataSource = resolveSpecifiedLookupKey(entry.getKey());

DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());

this.resolvedDataSources.put(lookupKey,dataSource);

}

if(this.defaultTargetDataSource!=null){

this.resolvedDefaultDataSource=resolveSpecifiedDataSource(this.defaultTargetDataSource);

}

}

疑问2:afterPropertiesSet()在哪里调用的呢?

答案:abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

就是这个接口InitializingBean起的作用,我们看这个接口

package org.springframework.beans.factory;

public interface InitializingBean{

void afterPropertiesSet() throws Exception;

}

这个接口的注释说:在所有bean属性初始化之后,由BeanFactory调用(BeanFactory是个接口,具体哪个类调用的就没有找了)。也就是说在
程序启动时就已经调用了这个方法,对resoolvedDataSources和resolvedDefaultDataSource初始化了。

 

疑问3:afterPropertiesSet()方法中的targetDataSoruces和defaultTargetDataSource在什么时候赋值的呢?

答案:本类中提供了这两个属性的set方法,但并没有地方调用set方法或者直接给这两个属性赋值。那么这两个属性的作用是什么呢?targetDataSources
是所有备选数据源,而defaultTargetDataSoruce是默认数据源。这些都是我们自己搭建数据库的信息,程序是不可能事先知道它们的值的,所 以就需要我们来告诉程序这些值。恰好本类中提供了set方法,所以我们自然会想到用spring的IOC来注入这些属性。abstract类是不能直接注
入属性的,所以我们要自己写一个AbstractRoutingDataSource的子类,并且注入targetDataSources和defaultTargetDataSource,这两个属性就随
着容器的启动而被赋值了。

 

疑问4:determineTargetDataSource()方法中Object lookupKey = determineCurrentLookupKey();其中determineCurrentLookupKey()是抽象方法,没有实现
啊,那lookupKey怎么能拿到值呢?

答案:在疑问3中我们说了,要写一个子类,而determineCurrentLookupKey()这个方法就需要在子类中重写,并且子类中只有这一个方法是必须要写的 方法。这个方法需要返回数据源的key值,父类需要这个key值来找到key对应的DataSource对象。并且这个方法也是关键的入口方法,也就
是说子类中返回哪个key就能使用哪个数据源,而具体是怎么使用数据源的,父类中已经帮我们做好了。那么我们编写的子类就要设计在什么 情况下进行切换数据源(key),并且返回这个key。

 

二、切换数据源的代码设计

1、编写AbstractRoutingDataSource的子类,重写determineCurrentLookupKey()方法,返回要使用的数据源的key

2、编写一个数据源转换器,提供切换数据源的方法

3、用Spring的AOP对Service层方法拦截,涉及增、删、改的方法切换到master数据源,查询的方法切换到slave数据源
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息