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数据源
读写分离的代码设计分两部分实现:
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数据源
相关文章推荐
- MySQL中的integer 数据类型
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- MySQL存储过程
- Android IPC进程间通讯机制
- Android之获取手机上的图片和视频缩略图thumbnails
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- mysql中int、bigint、smallint 和 tinyint的区别与长度
- mysql load data 导出、导入 csv
- source命令执行SQL脚本文件
- PropertyChangeListener简单理解