一,基于SSM框架的多数据源配置
1.创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
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(); } }
2.创建一个DynamicDataSource,继承AbstractRoutingDataSource并重写determineCurrentLookupKey
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { /** * override determineCurrentLookupKey * Description: 自动查找datasource */ @Override protected Object determineCurrentLookupKey() { //从自定义的位置获取数据源标识 return DynamicDataSourceHolder.getDataSource(); } }
3.配置多个数据源和第2步里创建的DynamicDataSource的bean
<!-- 引入jdbc配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> <!-- 配置数据源 --> <bean id="iFocusStructure" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <!-- 初始化连接大小 --> <property name="initialSize" value="${initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${maxActive}" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${maxIdle}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${maxWait}"/> </bean> <bean id="iFocusERA" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver2}"/> <property name="url" value="${url2}"/> <property name="username" value="${username2}"/> <property name="password" value="${password2}"/> <!-- 初始化连接大小 --> <property name="initialSize" value="${initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${maxActive}" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${maxIdle}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${maxWait}"/> </bean> <bean id="dataSource" class="com.yaming.hst.sys.util.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="iFocusStructure" value-ref="iFocusStructure"></entry> <entry key="iFocusERA" value-ref="iFocusERA"></entry> </map> </property> <property name="defaultTargetDataSource" ref="iFocusStructure" /> </bean>
4.代码如下:
@Service public class UserService { @Autowired private UserDao userDao; @Autowired private ProjectDao projectDao; public List<User> selectUser() { return userDao.selectUser(); } public List<Project> selectProject() { //切换到数据源iFocusERA DynamicDataSourceHolder.setDataSource("iFocusERA"); return projectDao.getAllProjects(); } }
5.
但是,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置。
6.定义一个名为DataSource的注解,作为切点:
import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; @Target({ ElementType.TYPE,ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }
7.配置切面,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); /** * 拦截目标方法,获取由@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.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) { logger.error(clazz + ":" + e.getMessage()); } } }
8.最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法
<bean id="dataSourceAspect" class="com.yaming.hst.sys.util.DataSourceAspect" /> <aop:config> <aop:aspect ref="dataSourceAspect"> <!-- 拦截所有service方法 --> <aop:pointcut id="dataSourcePointcut" expression="execution(* com.yaming.hst.*.dao.*.*(..))"/> <aop:before pointcut-ref="dataSourcePointcut" method="intercept" /> </aop:aspect> </aop:config>
9. 这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。
@DataSource("iFocusERA") public interface FeatureBigDataVersionMapper { int deleteByPrimaryKey(Integer id); int insert(FeatureBigDataVersion record); }
10. 提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。
11.jdbc.properties
driver= url= username= password= driver2= url2= username2= password2= initialSize=0 maxActive=20 maxIdle=5 minIdle=1 maxWait=60000