Spring+Mybatis实现动态数据源配置

Spring+Mybatis实现动态数据源配置

 

第一次接触电子商城的项目,所以也是第一次碰到读写要分离的需求,搞了半天时间终于搞定了,跟大家分享下,如果有不完整的地方,望大家见谅并提出宝贵的建议,谢谢!

好的,我们开始正题吧!

完成这一需求的思路图,如下图:

Spring+Mybatis实现动态数据源配置_第1张图片

好处在于,当我们添加多个数据源时,不需要添加多个sessionFactory。

使用到的技术有:annotation,Spring AOP ,反射。

---------------------------------------------------------------

 

   1、配置数据源,这个举例两个,如下代码:

        <!-- 配置第一个数据源 -->
	<!-- 数据源 : DriverManagerDataSource -->
	<bean id="dataSourceOne" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${driverClass}" />
		<property name="url" value="${url}" />
		<property name="username" value="${username}" />
		<property name="password" value="${password}" />
		<!-- 初始化连接大小 -->
		<property name="initialSize" value="0"></property>
		<!-- 连接池最大数量 -->
		<property name="maxActive" value="20"></property>
		<!-- 连接池最大空闲 -->
		<property name="maxIdle" value="20"></property>
		<!-- 连接池最小空闲 -->
		<property name="minIdle" value="1"></property>
		<!-- 获取连接最大等待时间 -->
		<property name="maxWait" value="60000"></property>
	</bean>

	<!-- 配置第二个数据源 -->
	<!-- 数据源 : DriverManagerDataSource -->
	<bean id="dataSourceTwo" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${driverClass1}" />
		<property name="url" value="${url1}" />
		<property name="username" value="${username1}" />
		<property name="password" value="${password1}" />
		<!-- 初始化连接大小 -->
		<property name="initialSize" value="0"></property>
		<!-- 连接池最大数量 -->
		<property name="maxActive" value="20"></property>
		<!-- 连接池最大空闲 -->
		<property name="maxIdle" value="20"></property>
		<!-- 连接池最小空闲 -->
		<property name="minIdle" value="1"></property>
		<!-- 获取连接最大等待时间 -->
		<property name="maxWait" value="60000"></property>
	</bean>

   2、接下来我们就要配置SqlSession 工厂(SqlSessionFactoryBean

    但是这边要注意的是,因为我们要有多个数据源,所以这边有所不同的是我们不能直接引用上面定义好的两个数据源,而是要引用我们自定义的动态数据源,看代码如下:

   

 <bean id="dynamicDataSource" class="com.zgn.common.DynamicDataSource">
		<property name="defaultTargetDataSource" ref="dataSourceOne" />
		<property name="targetDataSources">
			<map>
				<entry key="dataSourceOne" value-ref="dataSourceOne" />
				<entry key="dataSourceTwo" value-ref="dataSourceTwo" />
			</map>
		</property>
	</bean>

	<!-- mybatis 的 SqlSession 的工厂: SqlSessionFactoryBean -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dynamicDataSource" />
		<!-- 配置mybatis的映射文件 -->
		<property name="mapperLocations">
			<array>
				<value>classpath:com/zgn/*/mappers/*.xml</value>
			</array>
		</property>
		<property name="typeAliasesPackage" value="com.zgn" />
	</bean>

    3、那好啦,问题来了,com.zgn.common.DynamicDataSource这个类到底是什么呢??

 这边就涉及到了一个AbstractRoutingDataSource类,我们来看下它的结构是怎么样的?

   

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {  
  
}

    它继承了AbstractDataSource,而AbstractDataSource又是javax.sql.DataSource的子类,我们在往里面看看这个getConnection方法:

    

public Connection getConnection() throws SQLException {  
    return determineTargetDataSource().getConnection();  
} 

   当你看到determineTargetDataSource时,我想您也猜到怎么回事了吧,哈哈!!!!

   那我们就看一眼,就一眼,看它里面是什么?好奇吧

  

protected DataSource determineTargetDataSource() {  
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
    Object lookupKey = determineCurrentLookupKey();  
    DataSource dataSource = (DataSource) this.resolvedDataSources.get(lookupKey);  
    if (dataSource == null) {  
        dataSource = this.resolvedDefaultDataSource;  
    }  
    if (dataSource == null) {  
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
    }  
    return dataSource;  
}  

   有没有?有没有?看到determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource,我们再结合看下上面贴出来的部分代码回顾下

   

<bean id="dynamicDataSource" class="com.zgn.common.DynamicDataSource">
		<property name="defaultTargetDataSource" ref="dataSourceOne" />
		<property name="targetDataSources">
			<map>
				<entry key="dataSourceOne" value-ref="dataSourceOne" />
				<entry key="dataSourceTwo" value-ref="dataSourceTwo" />
			</map>
		</property>
	</bean>

   4、所以说了那么多,我们到底怎么写这个动态数据源类呢?com.zgn.common.DynamicDataSource

 继承AbstractRoutingDataSource重写determineCurrentLookupKey方法,还是看如下代码吧:

package com.zgn.common;

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

public class DynamicDataSource extends AbstractRoutingDataSource{

	@Override
	protected Object determineCurrentLookupKey() {
		//从自定义的位置获取数据源标识
		return DynamicDataSourceHolder.getDataSource();
	}
}

 5、好了问题又来了,这方法里面返回的是DynamicDataSourceHolder.getDataSource();方法,在哪里?在哪里?在这里,哈哈

 

package com.zgn.common;

public class DynamicDataSourceHolder {
	
	/**
	     注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
	*/
	private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();

	// 设置数据源
	public static void setDataSource(String dataSource) {
		dataSources.set(dataSource);
	}

	// 获取数据源
	public static String getDataSource() {
		return dataSources.get();
	}

	// 清除数据源
	public static void clearDataSource() {
		dataSources.remove();
	}
}

 6、那我们在什么时候调用setDataSource方法来动态切换数据源呢?(手动切换直接调用方法就好了,这里不多说了哈),接下来才是真正的高潮部分,这里我们用到了自定义注解(只需要在类或者方法上面写上注解就好了。),先定义一个注解类

 

package com.zgn.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default DataSource.master;
 
    public static String master = "dataSourceOne";
 
    public static String dataSourceTwo = "dataSourceTwo";
 
}

 7、DataSource 接口类创建完还不够哈,这样使用是不会有效果的,接下来我们就要用到AOP了,创建一个DataSourceAspect切面类,获取由@DataSource指定的数据源标识,进行自动切换数据源(其实就是调用DynamicDataSourceHolder.setDataSource方法

 

public class DataSourceAspect {
	/**
	 * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
	 * 
	 * @param point
	 * @throws Exception
	 */
	public void intercept(JoinPoint point) throws Exception {
		Class<?> target = point.getTarget().getClass();
		MethodSignature signature = (MethodSignature) point.getSignature();
		// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
		for (Class<?> clazz : target.getInterfaces()) {
			resolveDataSource(clazz, signature.getMethod());
		}
		resolveDataSource(target, signature.getMethod());
	}

	/**
	 * 提取目标对象方法注解和类型注解中的数据源标识
	 * 
	 * @param clazz
	 * @param method
	 */
	private void resolveDataSource(Class<?> clazz, Method method) {
		try {
			Class<?>[] types = method.getParameterTypes();
			// 默认使用类型注解
			if (clazz.isAnnotationPresent(DataSource.class)) {
				DataSource source = clazz.getAnnotation(DataSource.class);
				DynamicDataSourceHolder.setDataSource(source.name());
			}
			// 方法注解可以覆盖类型注解
			Method m = clazz.getMethod(method.getName(), types);
			if (m != null && m.isAnnotationPresent(DataSource.class)) {
				DataSource source = m.getAnnotation(DataSource.class);
				DynamicDataSourceHolder.setDataSource(source.name());
			}
		} catch (Exception e) {
			System.out.println(clazz + ":" + e.getMessage());
		}
	}
}

 8、好了,切面类写好后,我们还有一个很重要的步骤就是要在spring配置文件中进行配置才会有效果,

 在所有的接口中进行拦截使用

 <bean id="dataSourceAspect" class="com.zgn.common.DataSourceAspect" />
	<aop:config>
		<aop:aspect ref="dataSourceAspect">
			<!-- 拦截所有dao方法 -->
			<aop:pointcut id="dataSourcePointcut"
				expression="execution(* com.zgn.*.dao.*.*(..))" />
			<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
		</aop:aspect>
	</aop:config>

 9、以上步骤做完就大功告成了,那怎么使用呢?

   

@DataSource(name=DataSource.master)
public interface UserDao {

   只要在dao层的接口类上写上自定义的注解就好了,当然了,也可以写在方法上。

   搞定收工!!!!

   对了,这边有使用到一个关键类aspectjweaver.jar,附上了供亲们下载哈,不用谢!!!

 

你可能感兴趣的:(spring,mybatis,动态数据源)