spring --读写分离

*爱你&永不变心* 提交于 2019-12-05 03:39:32

实现流程

1.ssm基本流程
1.1 一个http请求,被springMVC的DispatcherServlet拦截,然后交给HandleRequest处理。
1.2 HandleRequest调用HandlerMapping与HandlerAdapter两个类,去匹配controller上的路径。
1.3 一旦匹配上,就执行该路径对应的方法。
1.4 controller层只是起一个控制作用,实际的业务处理交给service实现层。
1.5 实现层处理完数据,将结果返回给controller层。
这是一个基本的ssm流程
2.实现读写分离  
2.1 配置一个动态数据源切面,将该切面添加到service上。
2.2 一旦有请求进来,执行到service层,该切面进行拦截,能够获取到请求方法的名称和参数。
2.3 这里我们只需要方法名称就够了,实际上这里拿到的方法名称与请求参数可以做缓存(即是spring缓存原理)。该切面完成的功能是:根据执行方法名称判断应该走读库还是走写库,这里返回的是一个标记(读库还是写库的标记)。
2.4 接着有一个动态数据源的bean,该bean继承spring的AbstractRoutingDataSource类,并且覆写该类的determineCurrentLookupKey方法,返回读写库标记。同时该bean配置在spring-mybatis.xml文件中,由于该bean继承自AbstractRoutingDataSource类,该类有个属性targetDataSources,这个属性的类型是map,在这个map中设置读写标记对应读写数据源,根据刚刚传进来的标记来匹配该map中的key值,根据这个key值走不同的数据源。
2.5 spring-mybatis.xml中配置两套数据源,上述的key值就是走的这两套数据源中的一个。

本文基于另一篇文章
https://my.oschina.net/u/2312022/blog/748490
spring读写分离其实就是mysql主从复制,只不过添加了动态切换数据源的功能。
参考文章
http://blog.csdn.net/jack85986370/article/details/51559232
这一篇好像更加不错
http://blog.csdn.net/u013632755/article/details/51557956#comments
###1.完成搭建ssm最小系统
参见我这篇文章 https://my.oschina.net/u/2312022/blog/743014
###2.添加master包
输入图片说明
###3.DataSourceAspect

package com.test.spring.master;

import org.aspectj.lang.JoinPoint;

public class DataSourceAspect {
	
	/**
	 * 进入service方法之前
	 */
	public void before2(JoinPoint point) {
		String methodName = point.getSignature().getName();
		if (isSlave(methodName)) {
			//走写库
			DynamicDataSourceHolder.markMaster();
		}else {
			//走读库
			DynamicDataSourceHolder.markSlave();
		}
	}
	
	/**
	 * 判断是否为读库
	 */
    private Boolean isSlave(String methodName) {  
        // 方法名以query、find、get开头的方法名走从库 
    	boolean flag = true;
    	
    	if (methodName.contains("query")||
    		methodName.contains("find") ||
    		methodName.contains("get")) {
    		flag = false;
		}
    	return flag;
    }
}

###4.DynamicDataSource

package com.test.spring.master;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{

	@Override
	protected Object determineCurrentLookupKey() {
		System.out.println("----------DynamicDataSource---------");
		return DynamicDataSourceHolder.getDataSourceKey();
	}

}

###5.DynamicDataSourceHolder

package com.test.spring.master;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DynamicDataSourceHolder {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
	
	//写库对应的数据源key
	private static final String MASTER = "master";
	
	//读库对应的数据源key 
	private static final String SLAVE = "slave";
	
    //使用ThreadLocal记录当前线程的数据源key  
    private static final ThreadLocal<String> holder = new ThreadLocal<String>(); 
	
    /** 
     * 设置数据源key 
     * @param key 
     */  
    public static void putDataSourceKey(String key) {
    	if (LOGGER.isDebugEnabled()) {
    		LOGGER.debug("------切换数据源,key:"+key+"-------");
		}
        holder.set(key);  
    }
    
    /** 
     * 获取数据源key 
     * @return 
     */  
    public static String getDataSourceKey() {  
        return holder.get();  
    }
    
    /** 
     * 标记写库 
     */  
    public static void markMaster(){  
        putDataSourceKey(MASTER);  
    }  
      
    /** 
     * 标记读库 
     */  
    public static void markSlave(){  
        putDataSourceKey(SLAVE);  
    }
}

###6.db.properties

driverClassName=com.mysql.jdbc.Driver
#主数据库
master_jdbc_url=jdbc:mysql://192.168.2.11:3306/test?useUnicode=true&characterEncoding=UTF-8
master_jdbc_username=root
master_jdbc_password=123456

#从数据库
slave_jdbc_url=jdbc:mysql://192.168.2.11:3307/test?useUnicode=true&characterEncoding=UTF-8
slave_jdbc_username=root
slace_jdbc_password=654321

注意:mysql搭建在同一台主机上,仅仅端口不同
###7.spring-mybatis.xml

<?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:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

	<!-- 读取配置文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- 配置master数据源 -->
	<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${driverClassName}"></property>
		<property name="url" value="${master_jdbc_url}"></property>
		<property name="username" value="${master_jdbc_username}"></property>
		<property name="password" value="${master_jdbc_password}"></property>
	</bean>
	
	<!-- 配置slave数据源 -->
	<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${driverClassName}"></property>
		<property name="url" value="${slave_jdbc_url}"></property>
		<property name="username" value="${slave_jdbc_username}"></property>
		<property name="password" value="${slace_jdbc_password}"></property>
	</bean>
	
	<!-- 动态数据源 -->
	<bean id="dataSource" class="com.test.spring.master.DynamicDataSource">
		<!-- 设置多个数据源 -->
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<!-- 这里的key要和程序中保持一致 -->
				<entry key="master" value-ref="masterDataSource"></entry>
				<entry key="slave" value-ref="slaveDataSource"></entry>
			</map>
		</property>
		<!-- 默认数据源 -->
		<property name="defaultTargetDataSource" ref="masterDataSource"></property>
	</bean>
	
	
	<!-- 配置sqlSessionFactory -->
	<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 数据源 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 自动扫描mapping.xml文件 -->
		<property name="mapperLocations" value="classpath:com/test/spring/mapper/*.xml" />
	</bean>
	<!-- 配置mapper扫描器 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 扫描包路径,如果需要扫描多个包,用英文 逗号隔开 -->
		<property name="basePackage" value="com.test.spring.mapper" />
		<!-- 关联mapper扫描器 与 sqlsession管理器 -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean" />
	</bean>

		
	<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置事务属性 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 定义查询方法只读 -->
			<tx:method name="query*" read-only="true"/>
			<tx:method name="find*" read-only="true"/>
			<tx:method name="get*" read-only="true"/>
			
			<!-- 主数据库操作 -->
			<tx:method name="save*" propagation="REQUIRED"/>
			<tx:method name="update*" propagation="REQUIRED"/>
			<tx:method name="delete*" propagation="REQUIRED"/>
			
			<!-- 其它方法使用默认事务策略 -->
			<tx:method name="*"/>
			
		</tx:attributes>
	</tx:advice>
	
	<!-- 配置切面 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.test.spring.service.impl.*Impl.*(..))" id="txPointcut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
	</aop:config>
	
	<!-- 切面bean配置 -->
 	<bean id="dataSourceAspect" class="com.test.spring.master.DataSourceAspect"></bean>
	<aop:config>
		<aop:pointcut expression="execution(* com.test.spring.service.*.*(..))" id="aspectPointcut"/>
 		<aop:aspect ref="dataSourceAspect" order="-9999">
			<aop:before method="before2" pointcut-ref="aspectPointcut"/>
		</aop:aspect>
	</aop:config>
	
</beans>

###8.实现层

package com.test.spring.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.test.spring.bean.User;
import com.test.spring.mapper.UserMapper;
import com.test.spring.service.UserService;

@Service
public class UserServiceImpl implements UserService {
	
	@Autowired
	private UserMapper userMapper;
	
	/**
	 * 获取用户列表
	 */
	@Override
	public void getUserList() {
		
		System.out.println("走 从数据库");
		
		List<User> listUser = userMapper.getUserList();
		System.out.println(listUser);
		
		System.out.println("================");
	}

	/**
	 * 更新用户信息
	 */
	@Override
	public void updateUser(String userId,String userName) throws Exception{
		System.out.println("走 主数据库");
		int sucess = userMapper.updateUser(userId, userName);
		System.out.println(sucess);
		throw new RuntimeException();
	}
}

###9.测试
亲测,
1.可以实现动态切换数据库
2.事务也可以起效
3.主从同步在mysql那一篇已经实现

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!