您的位置:首页 > 编程语言 > Java开发

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

2017-10-19 13:43 507 查看
读写分离的好处:高并发互联网下减少数据库压力。详细请自行百度。

现在需求:读数据从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"> 
<co
4000
ntext: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形式?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: