线上业务中,通常WebServer可以通过横向扩展服务器来应对并发,但是,最终所有的请求还是要进入数据库,所以,数据库常常就成为整个并发的瓶颈。目前,互联网一般应对的方法,都是服务分组的思路,再加上各种缓冲集群的加持。微服务分组的方式,一般都是按照行业业务决定的,以我所处的医疗影像行业为例,常会设计出,用户中心服务集群,付款服务集群,RIS服务集群,报告服务,PACS服务,预约,排队叫号等等子服务。但是,随着业务量的增大,一些主业务服务数据库又不能满足我们日常的查询速度,当然也按照上边的方法做成一个子服务集群,又苦于两个服务组由于业务的关系密切,交互访问太多。这时,我们还可以使用传统的分库分表方法;而且,此方案,还能应对医疗项目私有化部署时,部署简单的情况。这时,就需要我们在服务器中同时连接两个数据库。主要是实战操作,所以,不啰嗦,直接上代码。
首先,在数据源配置文件中,增加多个配置;在SpringMVC框架配置中,配置多个BasicDataSource,通过配置AbstractRoutingDataSource,来对多个数据源进行路由,SqlSessionFactoryBean,MapperScannerConfigurer,DataSourceTransactionManager配置还是和单数据源一样;同时,需要注入Aop的Bean并且,配置切口映射函数。
1 配置文件
1.1 jdbc.properties
1.2 applicationContext.xml 配置数据库,以及在框架中注入的类
具体的配置的含义,都放在注释中
<context:component-scan base-package="com.qc,com.print"></context:component-scan>
<context:annotation-config ></context:annotation-config>
<!-- 引入jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 数据源配置 -->
<bean id="dataSourceRis" 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="5" /> <!-- 初始连接数量 -->
<property name="maxActive" value="30" /> <!-- 最大连接数量 -->
<property name="maxIdle" value="5" /> <!-- 空闲连接数量 -->
<property name="maxWait" value="60000" /> <!-- 一个查询1分钟内没有返回,自动放弃 -->
<property name="validationQuery" value="SELECT 1" /> <!-- 数据库连接可用性测试语句 -->
<property name="testOnBorrow" value="true" /> <!-- 每次获取一个连接的时候,验证一下连接是否可用,语句在validationQuery里面 -->
<property name="removeAbandoned" value="true" /> <!-- 自动处理连接未关闭的问题,Setting this to true can recover db connections from poorly written applications which fail to close a connection. -->
<property name="removeAbandonedTimeout" value="300" /> <!-- 连接使用后5分钟未关闭,则抛弃 -->
</bean>
<!-- 数据源配置 -->
<bean id="dataSourceQc" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${qc.driver}" />
<property name="url" value="${qc.url}" />
<property name="username" value="${qc.username}" />
<property name="password" value="${qc.password}" />
<property name="initialSize" value="5" /> <!-- 初始连接数量 -->
<property name="maxActive" value="30" /> <!-- 最大连接数量 -->
<property name="maxIdle" value="5" /> <!-- 空闲连接数量 -->
<property name="maxWait" value="60000" /> <!-- 一个查询1分钟内没有返回,自动放弃 -->
<property name="validationQuery" value="SELECT 1" /> <!-- 数据库连接可用性测试语句 -->
<property name="testOnBorrow" value="true" /> <!-- 每次获取一个连接的时候,验证一下连接是否可用,语句在validationQuery里面 -->
<property name="removeAbandoned" value="true" /> <!-- 自动处理连接未关闭的问题,Setting this to true can recover db connections from poorly written applications which fail to close a connection. -->
<property name="removeAbandonedTimeout" value="300" /> <!-- 连接使用后5分钟未关闭,则抛弃 -->
<!-- 每5分钟检查一次,每次检查3个连接,如果连接空闲时间达到5分钟,则认为可以Evict,从连接池排除
空闲的连接是否排除对连接池似乎没有太大影响,我们只需要保证每次获取的连接都可用,所以暂时先不开启
<property name="timeBetweenEvictionRunsMillis" value="300000" />
<property name="numTestsPerEvictionRun" value="3" />
<property name="minEvictableIdleTimeMillis" value="320000" />-->
</bean>
<!-- 动态数据源,数据源路由类 -->
<bean id="dataSourceRouter" class="com.wly.common.DataSourceRouter" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSourceRis" key="dataSourceRis"></entry>
<entry value-ref="dataSourceQc" key="dataSourceQc"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceRis" >
</property>
</bean>
<!-- 配置sqlSessionFactory,单例模式,整个框架只能有一个工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源的设置 -->
<property name="dataSource" ref="dataSourceRouter" />
<!-- 配置Mybatis的核心配置文件 -->
<property name="typeAliasesPackage" value="com.print.entity,com.qc.entity" />
<!-- 当mybatis的xml文件和mapper接口不在相同包下时,需要用mapperLocations属性指定xml文件的路径。
*是个通配符,代表所有的文件,**代表所有目录下 -->
<!-- property name="mapperLocations" value="classpath*:com/**/dao/*.xml"></property-->
<!-- 或者 -->
<!-- property name="mapperLocations">
<list>
<value>classpath:com/print/dao/*.xml</value>
<value>classpath:com/qc/dao/*.xml</value>
</list>
</property-->
</bean>
<!-- scan for mappers and let them be autowired Mapper 动态代理开发(mapper 和 dao必须放在同一个报下,并且名字相同,支持扫描) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 基础包,包下的所有接口全扫描 -->
<property name="basePackage" value="com.*.dao"></property>
<!-- 注入到工厂中 -->
<!-- property name="sqlSessionFactory" ref="sqlSessionFactory"></property-->
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceRouter" />
</bean>
1.3 context-dispatcher.xml 注入AOP的Bean和切口配置
<!-- 实例化切面类 -->
<bean id="transactionAop" class="com.wly.common.DataSourceAspect"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 切点,切点execution的函数条件(*表示返回值,com.qc.service..表示当前包,以及全部子包 *Inlay表示以Inlay为结尾的类.*(..) 表示不限制函数名字和形参 ) -->
<aop:pointcut expression ="(execution(* com.qc.service..*Inlay.*(..))) or (execution(* com.print.service..*.*(..)))" id= "transactionPointcut" />
<!-- 切面配置 -->
<aop:aspect ref="transactionAop">
<!-- 【环绕通知】
<aop:around method="arroud" pointcut-ref="transactionPointcut"/>-->
<!-- 【前置通知】 在目标方法之前执行 -->
<aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />
<!-- 【后置通知】 -->
<aop:after method="commit" pointcut-ref="transactionPointcut"/>
<!-- 【返回后通知】 -->
<aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>
</aop:aspect>
</aop:config>
注意,这个文件中的这几个配置
2 java类
java增加这4个类
CustomerContextHolder类
package com.wly.common;
public class CustomerContextHolder {
public static final String DATA_SOURCE_DEFAULT = "dataSourceRis";
public static final String DATA_SOURCE_QC = "dataSourceQc";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
DataSource类
package com.wly.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
DataSourceAspect类
package com.wly.common;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
public class DataSourceAspect {
public void beginTransaction(JoinPoint transactionPointcut) {
Class<?> target = transactionPointcut.getTarget().getClass();
MethodSignature signature = (MethodSignature) transactionPointcut.getSignature();
Method method = signature.getMethod() ;
DataSource dataSource = null ;
//从类初始化
dataSource = this.getDataSource(target, method) ;
//从接口初始化
if(dataSource == null){
for (Class<?> clazz : target.getInterfaces()) {
dataSource = getDataSource(clazz, method);
if(dataSource != null){
break ;//从某个接口中一旦发现注解,不再循环
}
}
}
if(dataSource != null && !"".equals(dataSource) ){
CustomerContextHolder.setCustomerType(dataSource.value());
}
System.out.println("[前置通知] 开启事务..");
}
public void commit() {
System.out.println("[后置通知] 提交事务..");
}
public void afterReturing(){
//使用完记得清空
CustomerContextHolder.setCustomerType(null);
System.out.println("[返回后通知]");
}
public void afterThrowing(){
System.out.println("[异常通知]");
}
// public void arroud(ProceedingJoinPoint pjp) throws Throwable{
// System.out.println("[环绕前:]");
// pjp.proceed(); // 执行目标方法
// System.out.println("[环绕后:]");
// }
/**
* 获取方法或类的注解对象DataSource
* @param target 类class
* @param method 方法
* @return DataSource
*/
public DataSource getDataSource(Class<?> target, Method method){
try {
//1.优先方法注解
Class<?>[] types = method.getParameterTypes();
Method m = target.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
return m.getAnnotation(DataSource.class);
}
//2.其次类注解
if (target.isAnnotationPresent(DataSource.class)) {
return target.getAnnotation(DataSource.class);
}
} catch (Exception e) {
e.printStackTrace();
}
return null ;
}
}
DataSourceRouter类
package com.wly.common;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DataSourceRouter extends AbstractRoutingDataSource{
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
String strType = CustomerContextHolder.getCustomerType();
return strType;
}
}
另外,在需要切换数据源的Service中,需要增加注释
3 对于项目来说需要增加几个jar包
上边的这几个jar包,各位朋友可以从本人的上传的资源里下载。https://download.csdn.net/download/rendawei636/15407179
实际项目中,调试后,是可以正确的去切换数据库。