Springmvc+mybatis+spring多資料來源配置 AOP+註解方式切換資料來源
阿新 • • 發佈:2019-01-25
紙上得來終覺淺,絕知此事要躬行
最終效果就是,通過在service層類或方法上,註解宣告需要的資料來源名稱,從而實現切換資料來源的目的
理論知識就不說了,直接上程式碼(不支援分散式事務)*
1.準備多資料來源properties檔案
<!-- 資料庫1 -->
customer.jdbc.driver = com.mysql.jdbc.Driver
customer.jdbc.url = jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=utf-8
customer.jdbc.username = root
customer.jdbc.password = root
<!-- 資料庫2 -->
...
2.spring注入資料來源
<!-- 資料庫1 -->
<bean id="customer" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >
<property name="driverClass" value="#{database['customer.jdbc.driver']}" />
<property name="jdbcUrl" value="#{database['customer.jdbc.url']}" />
<property name="user" value="#{database['customer.jdbc.username']}" />
<property name="password" value="#{database['customer.jdbc.password']}"/>
</bean>
<!-- 資料庫2 -->
...
3.自定義DataSourceContextHolder類
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* @description 提供給AOP去設定當前的執行緒的資料來源的資訊
* @param dbType
*/
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
/**
* @description 提供給AbstractRoutingDataSource的實現類,通過key選擇資料來源
* @return
*/
public static String getDbType() {
return (contextHolder.get());
}
/**
* @description 使用預設的資料來源
*/
public static void clearDbType() {
contextHolder.remove();
}
}
4.自定義DynamicDataSource類繼承自AbstractRoutingDataSource抽象類並實現determineCurrentLookupKey方法
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
5.spring中注入多資料來源
<bean id="dynamicDataSource" class="com.core.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="customer" key="customer"></entry>
...
</map>
</property>
<property name="defaultTargetDataSource" ref="customer">
</property>
</bean>
6.sqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="typeAliasesPackage" value="com.mapper1,com.mapper2" />
<property name="mapperLocations">
<list><!-- mapperLocations是陣列型別 list array都可以 示例為多路徑複雜情況 -->
<value>classpath:/mybatis/*Mapper.xml</value>
<value>classpath:/mybatis/base/*Mapper.xml</value>
</list>
</property>
</bean>
7.sqlSessionTemplate通過sqlSessionFactory初始化
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
8.掃描所有mybatis的介面
<!-- 掃描basePaclage下所有mapper介面 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 若使用sqlSessionFactoryBeanName,則忽略上一步
有小夥伴發現這個成員變數會導致資料庫切換失敗,我本人測試沒問題
個人覺得由於事務和資料來源的切換順序導致的問題居多
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
-->
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate" />
<property name="basePackage" value="com.dao1, com.dao2"></property>
</bean>
9.自定義AOP動態切換資料來源切面類DataSourceAspect
方式1:採用註解方式,自定義註解做為切點標記
自定義註解DataSource
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface DataSource {
String value();
}
//使用自定義切換資料庫註解
@DataSource(value="customer")
@Service
public class CustomerService
/**
* AOP動態切換資料來源切面類
* @author user
*
*/
@Component//<!-- 確認是否配置了該路徑下注釋配置bean的自動掃描 -->
@Order(0)//<!-- 設定切換資料來源的優先順序 -->
@Aspect
public class DataSourceAspect {
//@within在類上設定
//@annotation在方法上進行設定
//事務在service層 因此切換資料庫的註解只能放在service層或之前
@Pointcut("@within(com.core.DataSource)||@annotation(com.core.DataSource)")
public void pointcut() {}
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint)
{
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
DataSource annotationClass = method.getAnnotation(DataSource.class);
if(annotationClass == null){
annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
if(annotationClass == null) return;
}
String dataSourceKey = annotationClass.value();
if(dataSourceKey !=null){
DatabaseContextHolder.setCustomerType(dataSourceKey);
}
}
@After("pointcut()")
public void after(JoinPoint point) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
DatabaseContextHolder.clearCustomerType();
System.out.println("切換預設資料來源成功!");
}
}
注意:這裡有個坑,一定記得配置掃描器,確認已經存在的掃描路徑可以掃描到這個切面類
<!-- 採用註釋的方式配置bean -->
<context:component-scan base-package="com.core"></context:component-scan>
<!-- aop代理 true表示使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
方式2:採用xml配置方式,切點表示式作為切點 首先還是定義切面類
public class DataSourceAspect {
public void doBefore(JoinPoint joinPoint)
{
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
DataSource annotationClass = method.getAnnotation(DataSource.class);
if(annotationClass == null){
annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
if(annotationClass == null) return;
}
String dataSourceKey = annotationClass.value();
if(dataSourceKey !=null){
DatabaseContextHolder.setCustomerType(dataSourceKey);
}
}
public void after(JoinPoint point) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
DatabaseContextHolder.clearCustomerType();
System.out.println("切換預設資料來源成功!");
}
}
配置切面-切點-設定優先順序
<bean id="dataSourceAspect" class="com.core.DataSourceAspect"/>
<aop:config>
<!--資料來源的切換應在事務之前,事務在service層,
因此資料來源的切點至少在service層(或者controller層)-->
<aop:pointcut id="switchDataSource" expression="execution(* com.service..*.*(..)) "/>
<!--order的數值越小優先順序越高 -->
<aop:aspect ref="dataSourceAspect" order="0">
<aop:before method="doBefore" pointcut-ref="switchDataSource"/>
<aop:after method="after" pointcut-ref="switchDataSource"/>
</aop:aspect>
</aop:config>
設定事務的優先順序
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" order="1"/>