基于springAop动态切换数据源实现读写分离

8 篇文章 0 订阅
4 篇文章 0 订阅

读写分离的好处:高并发互联网下减少数据库压力。详细请自行百度。

现在需求:读数据从test库中,写数据从test2中。根据调用方法的不同实现动态切换。

直接代码:

bean.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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	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.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx.xsd">
	

   	<context:component-scan base-package="cn.rjx.spring.mutidatasource1"></context:component-scan>
   	<!-- ===================两个数据源============================ -->
	<bean name="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/test" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>
    <bean name="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/test2" />
        <property name="username" value="root" />
        <property name="password" value="123456"/>
    </bean>
    <!-- 动态数据源配置 -->
     <bean id="dynamicDataSource" class="cn.rjx.spring.mutidatasource1.DynamicDataSource">  
    	<property name="targetDataSources">  
	         <map key-type="java.lang.String">
	             <!-- 指定lookupKey和与之对应的数据源 -->
	             <entry key="test" value-ref="dataSource1"></entry>  
	             <entry key="test2" value-ref="dataSource2"></entry>  
	         </map>  
      	</property>  
    	 <!--默认的数据源 -->
    	 <property name="defaultTargetDataSource" ref="dataSource1" />  
 	</bean>  
    <!-- ==============mybatis配置=========================================== -->
 	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource" />
        <property name="mapperLocations" value="classpath:cn/rjx/spring/mutidatasource1/*Mapper.xml" />
    </bean>
    <!-- 配置扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描me.gacl.dao这个包以及它的子包下的所有映射接口类 -->
        <property name="basePackage" value="cn.rjx.spring.mutidatasource1" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
    
   
      <!-- ==============mybatis事务配置=========================================== -->
	<!-- 1.1.配置mybaties的事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
		<property name="dataSource" ref="dynamicDataSource" />
	</bean>
	<!-- 开启注解事务  下 -->
 	 <tx:annotation-driven transaction-manager="transactionManager"  /> 
 	<!-- 使用@aspectj 注解时要加上  或者使用@EnableAspectJAutoProxy   -->
	<aop:aspectj-autoproxy expose-proxy="true" />


</beans>









HandleDataSource.java:

/**
 * 把当前请求的数据源(xml配置targetDataSources下的key)塞入到ThreadLocal中
 */
public class HandleDataSource {
	public static final ThreadLocal<String> holder=new ThreadLocal<String>();

	public static void putDataSource(String dataSource){
		holder.set(dataSource);
	}
	/**
	 * ThreadLocal里面拿出当前请求的数据源
	 */
	public static String getDataSource(){
		return holder.get();
	}
	public static void clearDataSource() {
		holder.remove();
	}
}

DynamicDataSource.java:

/**
 * 数据源选择类:拿到动态切换的Key spring给你选择数据源
 * @author Administrator
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
	/**
	 * 告诉spring使用哪个数据源
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return HandleDataSource.getDataSource();
	}

}

注解类:在方法上使用,为方法指定一个数据源

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
	String value() default "";
}

service.java:使用事务时,传播属性很重要,要不调用的时候会在一个数据源上添加事务,就不对了。事务内部调用另一个事务时,走代理类的方法。(aop失效情况,查看我另一篇帖子)

@Service
public class ServiceImpl /*implements ServiceI*/ {
	@Autowired
	Dao dao;
	/**
	 *方法执行前对数据源进行动态切换
	 * @return
	 */
	
	@TargetDataSource("test")
	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public List<User> findUser(){
		List<User> list= dao.findUser();
		int a=10/0;
		return list;
	}
	
	
	@TargetDataSource("test2")
	@Transactional
	public void addOrder(){
		dao.addOrder();
		/**
		 * 当前数据源事务调用另一个数据源事务时,设置事务的传播属性,否则会在一个数据源上添加事务,那就不对了。
		 */
		ServiceImpl service=(ServiceImpl)AopContext.currentProxy();
		System.out.println(service.findUser());
	}
}

测试类;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:cn/rjx/spring/mutidatasource1/bean.xml"}) //加载配置文件   
@Component
public class Test01 {
	Logger logger=LoggerFactory.getLogger(ServiceImpl.class);
	@Resource
	ServiceImpl serviceImpl;
	
	@Test
	public void test01(){
		logger.info("#################################");
		List<User> list=serviceImpl.findUser();	
		System.out.println(list);
	}
	@Test
	public void test02(){
		logger.info("---------------------------------------");
		serviceImpl.addOrder();	
	}
}

(******)动态选择的切面:1.注解加到接口上,解析的时候根据接口上的注解解析。2.没接口情况下,注解直接加到实现方法上。我注释的地方是第一种情况,没注释的是加载实现类上的,也是我现在写的。


增强类:

@Aspect
@Order(-1)//多个aop配置时需要指定加载顺序(事务也是一个) -1为最先执行
@Component
//@EnableAspectJAutoProxy
public class DataSourceAspectJ  /*implements MethodBeforeAdvice,AfterReturningAdvice*/  {
	@Pointcut(value = "execution(* cn.rjx.spring.mutidatasource1.ServiceImpl.*(..))")
	public void join(){
	}
	@Before("join()")
	public void before(JoinPoint joinPoint){
		System.out.println("before advice!");
		//1.获取被代理类
		Object target = joinPoint.getTarget();
		//获取方法名称
		String targetMethodName=joinPoint.getSignature().getName();
		//2.拿到被代理类的接口
		//Class<?>[] interfacezz=target.getClass().getInterfaces();
		//3.拿到被代理的方法的入参
//		 Class<?>[] parameterTypes = ((MethodSignature)joinPoint.getSignature())
//								.getMethod().getParameterTypes();
		   Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
		   if(method.isAnnotationPresent(TargetDataSource.class)){
				TargetDataSource annotation = method.getAnnotation(TargetDataSource.class);
				HandleDataSource.putDataSource(annotation.value());
			}else{
				HandleDataSource.putDataSource("test");
			}
		 
		//4. 获取接口方法上的注解
//		 for(Class<?> intfzz:interfacezz){
//			 try {
//				Method method = intfzz.getMethod(targetMethodName, parameterTypes);
//				if(method!=null){
//					if(method.isAnnotationPresent(TargetDataSource.class)){
//						TargetDataSource annotation = method.getAnnotation(TargetDataSource.class);
//						HandleDataSource.putDataSource(annotation.value());
//					}else{
//						HandleDataSource.putDataSource("test");
//					}
//				}else{
//					continue;
//				}
//			 
//			 } catch (NoSuchMethodException e) {
//				e.printStackTrace();
//			} catch (SecurityException e) {
//				e.printStackTrace();
//			}
//		 }
	}
	@AfterReturning("join()")
	public void after(){
		System.out.println("after");
		HandleDataSource.clearDataSource();
	}
}


测试:执行test02()没异常情况下,往test2表中插入一条数据,并读取test中的的数据。如果有异常10/0事务回滚,两条操作都不执行。回滚指定事务段我没试,可以把我另一帖子的回滚段代码拿出来应该可以。


注意点:1.引入事务是切面那个order(-1)要加,表示位于增强链的最前。2.事务操作时要注意设置广播机制,尤其一个方法中调用另一个方法的事务时。


*************静态切换我研究研究,就是绑定sqlSessionFactory***************************



求助:能不能帮我把

  <!-- 动态数据源配置 -->
     <bean id="dynamicDataSource" class="cn.rjx.spring.mutidatasource1.DynamicDataSource">  
    	<property name="targetDataSources">  
	         <map key-type="java.lang.String">
	             <!-- 指定lookupKey和与之对应的数据源 -->
	             <entry key="test" value-ref="dataSource1"></entry>  
	             <entry key="test2" value-ref="dataSource2"></entry>  
	         </map>  
      	</property>  
    	 <!--默认的数据源 -->
    	 <property name="defaultTargetDataSource" ref="dataSource1" />  
 	</bean>  

转换成DynamicDataSource中的@Configuration+@bean形式?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值