zoukankan      html  css  js  c++  java
  • Spring实现多数据源配置

    一、前言

           对于小型项目,服务器与数据库是可以在同一台机子上的,但随着业务的庞大与负责,数据库和服务器就会分离开来。同时随着数据量的增大,数据库也要分开部署到多台机子上。

    二、Spring配置文件修改

           在理论学习与实践的差距:框架开源与不可逆的趋势[1]一文中曾经介绍过SSM框架,当时的框架采取单一数据源的配置,同时数据库的地址也没有写在properties文件中。但实际开发中,如果需要更换服务器的地址,修改XML文件会比较麻烦,一般都是提倡用properties文件,部署到正式服务器上,就只需要修改properties文件即可。

    1、applicationContext.properties文件:

    #Mysql
    jdbc.mysql.driver=com.mysql.jdbc.Driver
    jdbc.mysql.url=jdbc:mysql://localhost:3306/db_test
    jdbc.mysql.username=root
    jdbc.mysql.password=123456
    #Mysql2
    jdbc.mysql.driver2=com.mysql.jdbc.Driver
    jdbc.mysql.url2=jdbc:mysql://192.168.1.8:3306/db_test
    jdbc.mysql.username2=root
    jdbc.mysql.password2=123456

    2、动态数据源配置 [2]

    package com.test.utils;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource{
    
    	public static final String mySqlDataSource = "mySqlDataSource";
    	
    	public static final String mySqlDataSource2 = "mySqlDataSource2";
    	
    	//本地线程,获取当前正在执行的currentThread  
    	public 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();
    	}
    	
    	@Override
    	protected Object determineCurrentLookupKey() {
    		// TODO Auto-generated method stub
    		return getCustomerType();
    	}
    
    	
    }
    

    3、applicationContext.xml文件:

    添加了对配置文件applicationContext.properties的读取,同时在dynamicDataSource中添加两个或以上的数据源,并定义一个默认的数据源

    <?xml version="1.0" encoding="UTF-8"?>    
    <beans xmlns="http://www.springframework.org/schema/beans"    
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
        xmlns:p="http://www.springframework.org/schema/p"  
        xmlns:aop="http://www.springframework.org/schema/aop"   
        xmlns:context="http://www.springframework.org/schema/context"  
        xmlns:jee="http://www.springframework.org/schema/jee"  
        xmlns:tx="http://www.springframework.org/schema/tx"  
        xsi:schemaLocation="    
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd  
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd  
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd  
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">    
            
    	<!-- 自动扫描 -->
    	<context:component-scan base-package="com.test.dao" />
    	<context:component-scan base-package="com.test.service" />
    	
    	<!-- 读取配置文件 -->
    	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    	    <property name="locations">
    	        <list>
    	            <value>classpath:applicationContext.properties</value>
    	            <!-- <value>classpath:config.properties</value> -->
    	         </list>
    	    </property>
    	    <property name="ignoreUnresolvablePlaceholders" value="true"/>
    	</bean>
    	
    	<!-- 配置数据源 -->
    	<bean id="dataSource"
    		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<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}"/>
    	</bean>
    	
    	<bean id="dataSource2"
    		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<property name="driverClassName" value="${jdbc.mysql.driver2}"/>
    		<property name="url" value="${jdbc.mysql.url2}"/>
    		<property name="username" value="${jdbc.mysql.username2}"/>
    		<property name="password" value="${jdbc.mysql.password2}"/>
    	</bean>
    
    	<!-- 动态数据源 -->
    	<bean id="dynamicDataSource" class="com.test.utils.DynamicDataSource"><!--注意: 这里写选择数据源的类地址 下面跟着给出 -->
    		<property name="targetDataSources">
    			<map>
    				<entry key="mySqlDataSource" value-ref="dataSource" />
    				<entry key="mySqlDataSource2" value-ref="dataSource2" />
    			</map>
    		</property>
    		<property name="defaultTargetDataSource" ref="dataSource" /><!-- 设置默认为此mySqlDataSource数据源 -->
    	</bean>
    
    	<!-- 配置mybatis的sqlSessionFactory -->
    	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<property name="dataSource" ref="dynamicDataSource" />
    		<!-- 自动扫描mappers.xml文件 -->
    		<property name="mapperLocations" value="classpath:com/test/mappers/*.xml"></property>
    		<!-- mybatis配置文件 -->
    		<property name="configLocation" value="classpath:mybatis-config.xml"></property>
    	</bean>
    
    	<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<property name="basePackage" value="com.test.dao" />
    		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    	</bean>
    
    	<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
    	<bean id="transactionManager"
    		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<property name="dataSource" ref="dataSource" />
    	</bean>
    	
    	<!-- 配置事务通知属性 -->  
        <tx:advice id="txAdvice" transaction-manager="transactionManager">  
            <!-- 定义事务传播属性 -->  
            <tx:attributes>  
                <tx:method name="insert*" propagation="REQUIRED" />  
                <tx:method name="update*" propagation="REQUIRED" />  
                <tx:method name="edit*" propagation="REQUIRED" />  
                <tx:method name="save*" propagation="REQUIRED" />  
                <tx:method name="add*" propagation="REQUIRED" />  
                <tx:method name="new*" propagation="REQUIRED" />  
                <tx:method name="set*" propagation="REQUIRED" />  
                <tx:method name="remove*" propagation="REQUIRED" />  
                <tx:method name="delete*" propagation="REQUIRED" />  
                <tx:method name="change*" propagation="REQUIRED" />  
                <tx:method name="get*" propagation="REQUIRED" read-only="true" />  
                <tx:method name="find*" propagation="REQUIRED" read-only="true" />  
                <tx:method name="load*" propagation="REQUIRED" read-only="true" />  
                <tx:method name="*" propagation="REQUIRED" read-only="false" />  
            </tx:attributes>  
        </tx:advice>  
      
        <!-- 配置事务切面 -->  
        <aop:config>  
            <aop:pointcut id="serviceOperation"  
                expression="execution(* com.test.service.*.*(..))" />  
            <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />  
        </aop:config>  
    
    </beans>

    三、实际执行代码并动态切换数据源

    1、前台jsp页面:

    这里简单用a标签同步请求获取数据

    <a href="${pageContext.request.contextPath }/blog/searchAllBlog.do">获取列表</a>
    <table>
    	<c:choose>
    		<c:when test="${blogList==null }">
    			<tr>
    				<td>暂无列表</td>
    			</tr>
    		</c:when>
    		<c:otherwise>
    			<tr>
    				<th>&nbsp;&nbsp;&nbsp;&nbsp;</th>
    				<th>标题</th>
    				<th>内容</th>
    			</tr>
    			<c:forEach var="blog" items="${blogList }" varStatus="status">
    				<tr>
    					<td>${status.index+1 }</td>
    					<td>${blog.title }</td>
    					<td>${blog.content }</td>
    				</tr>
    			</c:forEach>
    		</c:otherwise>
    	</c:choose>
    </table>
    <hr>
    <a href="${pageContext.request.contextPath }/blog/searchAllBlog2.do">获取列表2</a>
    <table>
    	<c:choose>
    		<c:when test="${blogList2==null }">
    			<tr>
    				<td>暂无列表</td>
    			</tr>
    		</c:when>
    		<c:otherwise>
    			<tr>
    				<th>&nbsp;&nbsp;&nbsp;&nbsp;</th>
    				<th>标题</th>
    				<th>内容</th>
    			</tr>
    			<c:forEach var="blog" items="${blogList2 }" varStatus="status">
    				<tr>
    					<td>${status.index+1 }</td>
    					<td>${blog.title }</td>
    					<td>${blog.content }</td>
    				</tr>
    			</c:forEach>
    		</c:otherwise>
    	</c:choose>
    </table>

    2、Controller层

    	@RequestMapping("/searchAllBlog")
    	public String searchAllBlog(HttpServletRequest request){
    		List<Blog> blogList=blogService.searchAllBlog();
    		request.setAttribute("blogList", blogList);
    		return "add";
    	}
    	
    	@RequestMapping("/searchAllBlog2")
    	public String searchAllBlog2(HttpServletRequest request){
    		List<Blog> blogList=blogService.searchAllBlog2();
    		request.setAttribute("blogList2", blogList);
    		return "add";
    	}

    3、Service层

    public interface BlogService {
    
    	public int add(Blog blog);
    	
    	public List<Blog> searchAllBlog();
    	
    	public List<Blog> searchAllBlog2();
    }

    对应的实现类:[2]

    	@Override
    	public List<Blog> searchAllBlog() {
    		// TODO Auto-generated method stub
    		return blogDao.searchAllBlog();
    	}
    
    	@Override
    	public List<Blog> searchAllBlog2() {
    		// TODO Auto-generated method stub
    		List<Blog> list = null;
    		DynamicDataSource.clearCustomerType();//重点: 实际操作证明,切换的时候最好清空一下
    		DynamicDataSource.setCustomerType(DynamicDataSource.mySqlDataSource2);
    		list = blogDao.searchAllBlog();
    		DynamicDataSource.clearCustomerType();//
    		DynamicDataSource.setCustomerType(DynamicDataSource.mySqlDataSource);//切换回主数据源 
    		return list;
    	}

    当然也可以用其他方式来实现数据源的切换,详情请参看Spring+MyBatis多数据源配置实现[3]

    四、运行程序

           运行程序后,点击获取获取列表2,会发现不允许连接服务器。原因是由于Mysql默认只能本机访问,不能够远程连接。这里需要对MySql进行开启权限操作。[4]

    grant all on 数据库名.* to ‘数据库账户名’@’%’ identified by ‘密码’ with grant option;
    
    
    回车
    
    flush privileges;
    
    回车
    注意:上面的单引号不能省,数据库名.* 表示要开放的数据库下所有表,如果该连接的所有数据库都要开放,可以用 *.* 代替。
    ‘数据库账户名’@’%’ 这里表示要开放的账户,百分号表示在任何主机都允许访问。
    如果以上两步均显示 “Query OK, 0 rows affected (0.00 sec)”,那么说明命令已经成功执行,现在就可以远程连接你的mysql数据库了。

    五、总结

           随着业务的复杂增加,一般都会采取多数据源的方式,减轻服务器压力。一般来说Mysql占用系统资源比较少,对服务器性能影响不大,如果需要多种不同类型的数据库,那么分别部署到不同的服务器上可以优化性能,提供系统的运行速度。当然,如果项目过于巨大,会被变成分布式项目,将不同功能的子系统部署到不同服务器上。

    Reference:

    [1] ryelqy, 理论学习与实践的差距:框架开源与不可逆的趋势, https://blog.csdn.net/ryelqy/article/details/81368083

    [2] 现世安稳。SpringMVC配置多个数据源,  https://www.cnblogs.com/hero123/p/8945914.html

    [3] 懒惰的肥兔Spring+MyBatis多数据源配置实现https://www.cnblogs.com/lzrabbit/p/3750803.html

    [4] 沐浴星光mysql无法远程连接的解决方法https://www.cnblogs.com/star91/p/4980024.html

  • 相关阅读:
    简单查询
    Scott用户表
    记一次Sqoop抽数据异常
    Flink+Druid构建实时OLAP的探索
    客户端埋点实时OLAP指标计算方案
    Kafka服务不可用(宕机)问题踩坑记
    实时计算-多级订单金额,及下级人数
    Apache Druid0.15.0安装方式
    superset安装文档
    Scala的常用小技巧
  • 原文地址:https://www.cnblogs.com/ryelqy/p/10104038.html
Copyright © 2011-2022 走看看