spring+mybatis多数据源配置、读写分离

实现思路:在spring中配置多个数据源,然后在service层通过注解方式标明方法所要使用的数据源,利用springAOP在service方法执行前根据方法上的注解明确所要使用的数据源。如下图



以上分析可得出,需要抽象出一个 DynamicDataSource数据源,在spring中关于数据源的配置都是基于 DynamicDataSource,在运行时根据service上的注解从 DynamicDataSource选取需要的数据源进行实际的持久化操作,下面开始上代码

首先,介绍spring 的AbstractRoutingDataSource 

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

Java代码 
  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}  

 

Java代码  
  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {  
  2.   
  3.     private Map<Object, Object> targetDataSources;  
  4.   
  5.     private Object defaultTargetDataSource;  
  6.   
  7.     private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();  
  8.   
  9.     private Map<Object, DataSource> resolvedDataSources;  
  10.   
  11.     private DataSource resolvedDefaultDataSource;  

 

    AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。

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

Java代码  
  1. public interface DataSource  extends CommonDataSource,Wrapper {  
  2.   
  3.   Connection getConnection() throws SQLException;  
  4.    
  5.   Connection getConnection(String username, String password)  
  6.     throws SQLException;  
  7. }  

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

 

Java代码  
  1. public Connection getConnection() throws SQLException {  
  2.     return determineTargetDataSource().getConnection();  
  3. }  
  4.   
  5. public Connection getConnection(String username, String password) throws SQLException {  
  6.     return determineTargetDataSource().getConnection(username, password);  
  7. }  

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

 

Java代码   收藏代码
  1. protected DataSource determineTargetDataSource() {  
  2.         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
  3.         Object lookupKey = determineCurrentLookupKey();  
  4.         DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
  5.         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
  6.             dataSource = this.resolvedDefaultDataSource;  
  7.         }  
  8.         if (dataSource == null) {  
  9.             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
  10.         }  
  11.         return dataSource;  
  12.     }  

 

我们最关心的还是下面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。这样就达到了切换数据库的目的。

现在开始介绍实现方式:

1. 创建注解类,用来标记service方法所要使用的数据源key

  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.METHOD)  
  3. public @interface DataSource {  
  4.     String value();  
2. 创建DynamicDataSource类,该类继承自 AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
	private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();

	public static void setDataSourceKey(String dataSource) {
		dataSourceKey.set(dataSource);
	}

	public static String getDatasourcekey() {
		return dataSourceKey.get();
	}

	protected Object determineCurrentLookupKey() {
		return dataSourceKey.get();
	}

}


方法中实现了determineCurrentLookupKey()方法,该方法会从ThreadLocal对象中获取到一个key来表明所要使用的数据源

3. 实现springAop来根据service中方法上的注解设置ThreadLocal对象

import java.lang.reflect.Method;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.chinalife.clap.core.MultDataSource;
import com.chinalife.clap.core.annotation.DataSource;

public class DataSourceAspect {

	public void before(JoinPoint joinPoint) {
		if (TransactionSynchronizationManager.isActualTransactionActive()
				&& DynamicDataSource.getDatasourcekey() != null)
			return;
		// 获取方法签名
		Method declareMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
		Method instanceMethod = joinPoint.getTarget().getClass().getMethod(declareMethod.getName(),
				declareMethod.getParameterTypes());
		DataSource methodAnnotation = AnnotationUtils.findAnnotation(instanceMethod, DataSource.class);
		if (methodAnnotation == null)
			return;
		if (methodAnnotation != null) {
			MultDataSource.setDataSourceKey(methodAnnotation.value());
		}
	}

	/**
	 * 方法执行完后置空
	 */
	public void after(JoinPoint joinPoint) {
		if (TransactionSynchronizationManager.isActualTransactionActive())
			return;
		if (TransactionSynchronizationManager.isSynchronizationActive())
			TransactionSynchronizationManager.clearSynchronization();
		MultDataSource.setDataSourceKey(null);
	}

}



3.开始配置spring数据源 application-jdbc.xml

<bean id="masterdataSource"  
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/shop" />  
        <property name="username" value="root" />  
        <property name="password" value="yangyanping0615" />  
    </bean>  
  
    <bean id="slavedataSource"  
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />  
        <property name="username" value="root" />  
        <property name="password" value="yangyanping0615" />  
    </bean>  
      
        <beans:bean id="dynamicDataSource" class="com.air.shop.common.db.DynamicDataSource">  
        <property name="targetDataSources">    
              <map key-type="java.lang.String">    
                  <!-- write -->  
                 <entry key="master" value-ref="masterdataSource"/>    
                 <!-- read -->  
                 <entry key="slave" value-ref="slavedataSource"/>    
              </map>    
                
        </property>    
        <property name="defaultTargetDataSource" ref="masterdataSource"/>    
    </beans:bean>  
  
    <bean id="transactionManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dynamicDataSource" />  
    </bean>  
  
  
    <!-- 配置SqlSessionFactoryBean -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dynamicDataSource" />  
        <property name="configLocation" value="classpath:config/mybatis-config.xml" />  
    </bean>  
<!-- 配置数据库注解aop -->  
    <beans:bean id="manyDataSourceAspect" class="com.air.shop.proxy.DataSourceAspect" />  
    <aop:config>  
        <aop:aspect id="c" ref="manyDataSourceAspect">  
            <aop:pointcut id="tx" expression="execution(* com.air.shop.mapper.service.*(..))"/>  
            <aop:before pointcut-ref="tx" method="before"/>  
            <aop:after pointcut-ref="tx" method="after"/>  
         </aop:aspect> 
</aop:config> <!-- 配置数据库注解aop -->

下面给出service实例

public interface Userservic {  
    @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);  

}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值