spring+myBatisi实现多数据源动态切换

本文介绍了如何使用Spring的AOP和自定义注解来实现多数据源动态切换,解决了AOP与事务执行顺序及注解获取的问题。通过创建自定义注解、扩展AbstractRoutingDataSource以及定义数据源切面类,实现了读写两个数据源的动态切换。测试时发现注解必须在service接口中才能生效。

        目前网上实现动态数据源的文章很多,基本都是根据spring的AOP实现的,本来以为简单,复制粘贴即可,结果遇到不少问题,一是遇到AOP与事务之间执行顺序的问题,二是service实现类遇到注解无法获取的问题,前一个问题使用order解决了,后一个问题则是放到service接口类中就可以获取。具体如下:


1、自定义注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
 * RUNTIME
 * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
 * @author yangGuang
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
	String value();
}

2、基于spring的aop实现多数据源(读和写两个数据源):
1)写一个ChooseDataSource: 类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法

1
2
3
4
5
6
7
8
9
10
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class ChooseDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return HandleDataSource.getDataSource();
	}
	
}

2)利用ThreadLocal解决线程安全问题:
1
2
3
4
5
6
7
8
9
10
public class HandleDataSource {
	public static final ThreadLocal<String> holder = new ThreadLocal<String>();
	public static void putDataSource(String datasource) {
		holder.set(datasource);
	}
	
	public static String getDataSource() {
		return holder.get();
	}    
}

3)定义一个数据源切面类,通过aop访问,获取方法上的自定义注解,然后根据注解内容尽情判断,动态设置数据源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
//@Aspect
//@Component
public class DataSourceAspect {
	//@Pointcut("execution(* com.apc.cms.service.*.*(..))")  
	public void pointCut(){};  
	
  //  @Before(value = "pointCut()")
	 public void before(JoinPoint point)
		{
			Object target = point.getTarget();
			System.out.println(target.toString());
			String method = point.getSignature().getName();
			System.out.println(method);
			Class<?>[] classz = target.getClass().getInterfaces();
			Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
				.getMethod().getParameterTypes();
			try {
				Method m = classz[0].getMethod(method, parameterTypes);
				System.out.println(m.getName());
				if (m != null && m.isAnnotationPresent(DataSource.class)) {
					DataSource data = m.getAnnotation(DataSource.class);
					HandleDataSource.putDataSource(data.value());
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
}

4)配置applicationContext.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!-- 主库数据源 -->
 <bean id="writeDataSource" class="com.jolbox.bonecp.BoneCPDataSource"  destroy-method="close">
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
	<property name="jdbcUrl" value="jdbc:mysql://172.22.14.6:3306/cpp?autoReconnect=true"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
	<property name="partitionCount" value="4"/>
	<property name="releaseHelperThreads" value="3"/>
	<property name="acquireIncrement" value="2"/>
	<property name="maxConnectionsPerPartition" value="40"/>
	<property name="minConnectionsPerPartition" value="20"/>
	<property name="idleMaxAgeInSeconds" value="60"/>
	<property name="idleConnectionTestPeriodInSeconds" value="60"/>
	<property name="poolAvailabilityThreshold" value="5"/>
</bean>

<!-- 从库数据源 -->
<bean id="readDataSource" class="com.jolbox.bonecp.BoneCPDataSource"  destroy-method="close">
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
	<property name="jdbcUrl" value="jdbc:mysql://172.22.14.7:3306/cpp?autoReconnect=true"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
	<property name="partitionCount" value="4"/>
	<property name="releaseHelperThreads" value="3"/>
	<property name="acquireIncrement" value="2"/>
	<property name="maxConnectionsPerPartition" value="40"/>
	<property name="minConnectionsPerPartition" value="20"/>
	<property name="idleMaxAgeInSeconds" value="60"/>
	<property name="idleConnectionTestPeriodInSeconds" value="60"/>
	<property name="poolAvailabilityThreshold" value="5"/>
</bean>

<!-- transaction manager, 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>


<!-- 注解自动载入 -->
<context:annotation-config />

<!--enale component scanning (beware that this does not enable mapper scanning!)-->
<context:component-scan base-package="com.apc.cms.persistence.rdbms" />
<context:component-scan base-package="com.apc.cms.service">
 <context:include-filter type="annotation"  
	expression="org.springframework.stereotype.Component" />  
</context:component-scan> 

<context:component-scan base-package="com.apc.cms.auth" />

<!-- enable transaction demarcation with annotations -->
<tx:annotation-driven />


<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="typeAliasesPackage" value="com.apc.cms.model.domain" />
</bean>

<!-- scan for mappers and let them be autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.apc.cms.persistence" />
	<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<bean id="dataSource" class="com.apc.cms.utils.ChooseDataSource">
	<property name="targetDataSources">  
		  <map key-type="java.lang.String">  
			  <!-- write -->
			 <entry key="write" value-ref="writeDataSource"/>  
			 <!-- read -->
			 <entry key="read" value-ref="readDataSource"/>  
		  </map>  
		  
	</property>  
	<property name="defaultTargetDataSource" ref="writeDataSource"/>  
</bean>
  
<!-- 激活自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

<!-- 配置数据库注解aop -->
<bean id="dataSourceAspect" class="com.apc.cms.utils.DataSourceAspect" />
<aop:config>
	<aop:aspect id="c" ref="dataSourceAspect">
		<aop:pointcut id="tx" expression="execution(* com.apc.cms.service..*.*(..))"/>
		<aop:before pointcut-ref="tx" method="before"/>
	</aop:aspect>
</aop:config>
<!-- 配置数据库注解aop -->

3、测试,如果是在service中实现,需要放到接口类中,放到实现类中无法获取注解。

详情可见:http://www.iteye.com/problems/58681

public interface TestService {
	@DataSource("write")  
Document update(User user) throws Exception;
	
        @DataSource("read")  
        Document getDocById(long id) throws Exception;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值