使用spring註解在方法或類上動態切換數據源

1 相關知識

1.1  Spring的AbstractRoutingDataSource抽象類,該類可以充當數據源的路由中介,可以根據名字動態切換數據源

1.2  SpringAop

1.3  Spring自定義註解

2 思路

2.1 在類或方法上添加自定義註解,其值爲數據源的名字

2.2 通過SpringAop在項目運行時獲取到類或方法上自定義註解的值

2.3 將拿到的註解值通過數據源路由動態的設置數據源

3 實戰

3.1 首先實現數據源路由

3.1.1 通過繼承AbstractRoutingDataSource類來實現數據源切換

public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DataSourceContextHolder.getDbType();
	}

}

該類中只有一個方法,該方法通過返回數據源的名稱動態的切換數據源


3.1.2 通過ThreadLocal 線程本地變量設置數據源名稱來保證線程安全

public class DataSourceContextHolder {
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
	  
    public static void setDbType(String dbType) {  
           contextHolder.set(dbType);  
    }  
 
    public static String getDbType() {  
           return ((String) contextHolder.get());
    }  
 
    public static void clearDbType() {  
           contextHolder.remove();  
    }
}

ThreadLocal爲每個線程創建一個實例,使每個線程保持各自獨立的一個對象。通過contextHolder.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行contextHolder.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作爲map的key來使用的。在本列子中就是爲了保證多個人可以同時訪問不同的數據源。


3.1.3 數據源名稱管理類

public interface DataSourceName {
   static final String DATA_CHAT="test1"; //測試1庫
   static final String DATA_USER="test2"; //測試2庫
}
3.1.3 配置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: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-4.3.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!--測試1-->
    <bean id="test1" class="com.alibaba.druid.pool.DruidDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://XXXX/test1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true" />  
        <property name="username" value="root" />  
        <property name="password" value="isoftadmin" />  
        <!-- 初始化連接大小 -->  
        <property name="initialSize" value="1"></property>  
        <!-- 連接池最大數量 -->  
        <property name="maxActive" value="50"></property>  
        <!-- 連接池最大空閒 -->  
        <property name="maxIdle" value="2"></property>  
        <!-- 連接池最小空閒 -->  
        <property name="minIdle" value="1"></property>  
        <!-- 獲取連接最大等待時間 -->  
        <property name="maxWait" value="10000"></property>  
    </bean>
    <!--測試2-->
    <bean id="test2" class="com.alibaba.druid.pool.DruidDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://XXXX/test2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true" />  
        <property name="username" value="root" />  
        <property name="password" value="isoftadmin" />  
        <!-- 初始化連接大小 -->  
        <property name="initialSize" value="1"></property>  
        <!-- 連接池最大數量 -->  
        <property name="maxActive" value="50"></property>  
        <!-- 連接池最大空閒 -->  
        <property name="maxIdle" value="2"></property>  
        <!-- 連接池最小空閒 -->  
        <property name="minIdle" value="1"></property>  
        <!-- 獲取連接最大等待時間 -->  
        <property name="maxWait" value="10000"></property>  
    </bean>
     <!--數據源路由 DynamicDataSource就是自己編寫的數據源路由類 -->
     <bean id="dataSource" class="com.gcx.api.common.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="test1" value-ref="test1"/>
                <entry key="test2" value-ref="test2"/>
            </map>
        </property>
         <!-- 默認數據源爲test1 -->
        <property name="defaultTargetDataSource" ref="test1"/>
    </bean>
    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <!-- 自動掃描mapping.xml文件 -->  
        <property name="mapperLocations" value="classpath:com/gcx/api/mapping/*.xml"></property> 
    </bean> 
    <!-- DAO接口所在包名,Spring會自動查找其下的類 -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage" value="com.gcx.api.dao" />  
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>  
    </bean>
     <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx -->  
    <bean id="transactionManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
</beans>


3.2 編寫自定義註解

3.2.1 自定義註解類,用來在類或方法上設置數據源名稱

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomDataSource {
	String value();
}


3.3 編寫AOP

3.3.1 通過切面獲取註解的值

@Component
@Aspect
public class DataAop {
   
	@Before("execution(* com.gcx.api.service.impl.*.*(..))")
	public void switchDataSource(JoinPoint pjp) throws Throwable{
		Signature signature = pjp.getSignature();    
		MethodSignature methodSignature = (MethodSignature)signature;  
		Method targetMethod = methodSignature.getMethod();
		Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
		    //首先判斷方法級別
	    	CustomDataSource cds=realMethod.getAnnotation(CustomDataSource.class);
	    	if(cds==null){ //判斷類級別
	    		 cds= AnnotationUtils.findAnnotation(realMethod.getDeclaringClass(), CustomDataSource.class);
	    	}
	    	if(cds==null){  //默認庫
	    		DataSourceContextHolder.setDbType(DataSourceName.DATA_CHAT);
	    		return;
	    	}
	    	String dataSourceName=cds.value(); //獲取註解的值
               if(dataSourceName!=null&&!dataSourceName.equals(""))  //通過數據源路由類切換數據源
	    	      DataSourceContextHolder.setDbType(dataSourceName);
	}
	
}
該切面只攔截了Service實現層,所有註解需要寫在Service實現層的類或方法中。切面會在方法或類執行之前優先獲取方法上的註解,如果方法上沒有,則會去類上找,如果類上也沒有則會使用默認庫。


3.4 在項目中使用

3.4.1 在Service實現層使用自定義註解切換數據源

@CustomDataSource(DataSourceName.DATA_USER) //使用自定義註解切換數據源
@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserMapper userDao;

	// 查詢所有記錄 
	public MyResult findAllRecords(User record) throws Exception{
			List<User> list = userDao.findAllList(record);
			int count = (int) userDao.findAllListCount(record);
			return MyResult.ok(count, list);
	}

      //添加記錄      
       @CustomDataSource(DataSourceName.DATA_CHAT)
       public MyResult addRecord(User record) throws Exception{
                        // UUID主鍵 
                        long nanoTime = System.nanoTime(); 
                        record.setUserId(new BigDecimal(nanoTime));
                        int i = userDao.insertSelective(record);
                        return MyResult.ok(i, "新增");
       }

      // 刪除記錄
     public MyResult delRecord(String id) throws Exception{
                       int i = userDao.deleteByPrimaryKey(id);
                       return MyResult.ok(i, "刪除");
      }
}
該類的find和del方法會使用test2庫,add方法會使用test1庫。
如果需要全部使用test1庫,則在方法或類上無需定義任何自定義註解。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章