第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:
package spring; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSource(); } }第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
package spring; public class DynamicDataSourceHolder { private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:
<bean id="readServerDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.mysql.driver}"/> <property name="url" value="${jdbc.mysql.url}"/> <property name="username" value="${jdbc.mysql.username}"/> <property name="password" value="${jdbc.mysql.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <!--<property name="maxActive" value="${jdbc.maxActive}"/>--> <!--<property name="maxWait" value="${jdbc.maxWait}"/>--> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>--> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean> <bean id="writeServerDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.mysql.driver}"/> <property name="url" value="${jdbc.mysql.url}"/> <property name="username" value="${jdbc.mysql.username}"/> <property name="password" value="${jdbc.mysql.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <!--<property name="maxActive" value="${jdbc.maxActive}"/>--> <!--<property name="maxWait" value="${jdbc.maxWait}"/>--> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>--> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean>
<bean id="dataSource" class="spring.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="readServerDataSource" value-ref="readServerDataSource"></entry> <entry key="writeServerDataSource" value-ref="writeServerDataSource"></entry> </map> </property> <!-- 这里可以指定默认的数据源 --> <property name="defaultTargetDataSource" ref="readServerDataSource"/> </bean> <!-- MyBatis配置 --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自动扫描domain目录, 省掉Configuration.xml里的手工配置 --> <property name="typeAliasesPackage" value="order.dao.domain"/> <!-- 显式指定Mapper文件位置 --> <property name="mapperLocations" value="classpath*:mybatis/*.xml"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource(" readServerDataSource ")即可切换到数据源对其只进行读的操作,需要使用只要DynamicDataSourceHolder.setDataSource(" writeServerDataSource ")
接下来配置自定义注解用来切换数据源,这样操作起来更加的简便,不用每次都去set
首先,我们得定义一个名为DataSource的注解
package spring; import org.springframework.stereotype.Component; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
package spring; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @Component @Aspect 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((Class<? extends Annotation>) DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } // 方法注解可以覆盖类型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } }OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。
提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。
例子用读的数据源:
@Service("ProductDAOImpl") @DataSource("readServerDataSource") public class ProductDAOImpl extends MyAbstractPageService<IProductDAO, Product> { @Autowired private IProductDAO dao; @Override public IProductDAO getDao() { return dao; } public IProductDAO querySum() { return dao; } public IProductDAO queryId(String id) { return dao; } }然后自己写个数据库的操作测试一下是否成功