SpringMVC+Mybatis多个数据源

6 篇文章 0 订阅

       线上业务中,通常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

实际项目中,调试后,是可以正确的去切换数据库。

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于SpringSpring MVC和MyBatis的博客系统开发教程如下: 首先,要准备好开发所需的环境和工具。你需要安装Java开发工具包(JDK)、Eclipse或者IntelliJ IDEA集成开发环境、Apache Tomcat服务器、Maven构建工具以及MySQL数据库。确保这些工具都正确安装和配置。 接下来,创建一个新的Maven项目,并在pom.xml文件中添加依赖项,包括Spring MVC、MyBatis数据库连接池、日志等。这些依赖项可以通过Maven自动下载和管理。 然后,在src/main/java目录下创建相应的包结构,如controller、service、dao等。在dao包下创建相应的数据访问接口,并使用MyBatis提供的注解或XML配置文件实现数据访问的逻辑。在service包下创建对应的服务接口和实现类,用于处理业务逻辑。在controller包下创建控制器类,处理请求和响应。 配置SpringMyBatis的配置文件。在src/main/resources目录下创建一个名为applicationContext.xml的Spring配置文件,并添加相关的配置信息,包括数据库连接、事务管理、包扫描等。同样,在该目录下创建一个名为mybatis-config.xml的MyBatis配置文件,并配置数据源、映射文件等。 编写博客系统的前端页面和样式。可以使用HTML、CSS和JavaScript等技术来构建用户界面,使用JSP或Thymeleaf等模板引擎来动态生成页面内容。可以使用Bootstrap等前端框架来加快开发进度并提供良好的用户体验。 最后,测试和部署博客系统。使用JUnit等单元测试框架来测试各个模块的功能是否正常。将项目打包成war文件,并将其部署到Tomcat服务器上运行。 通过以上步骤,你就可以基于SpringSpring MVC和MyBatis来开发一个简单的博客系统了。当然,在实际开发中还会涉及到更多的细节和技术选择,需要持续学习和实践。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值