背景
需求是這樣,項目中有兩個數據庫,但是中間有一個商戶的配置表需要在從A項目同步到B項目,原先這些是通過接口的形式來完成,但是考慮到出現新增字段後者修改字段需要同時修改接口提供端以及接口調用端。
這裏採用在同一個項目中採用定時任務去調用兩個數據庫直接完成從A庫到B庫的數據複製。
這裏我使用的是Spring以及Mybatis來完成。
設計思想
首先得配置兩個數據源,在特定的方法調用時手動指定該方法訪問的數據源。
由於mybatis訪問數據源的方法都已經進了封裝,這裏我採用繼承後重寫設置數據源的方法,讓它能按我的需求使用不同的數據源。
下面的例子我們能看到,實際的DaoImpl類中,獲取sqlSession是通過繼承的父類(AbstractBaseMyBatisDao)的getSqlSession方法來實現。
進入到父類(AbstractBaseMyBatisDao)我們可以看到,這是一個抽象類,內部獲取sqlSession的方法也是來自他的父類 在mybatis-dao-framework.jar中的類(SqlSessionDaoSupport)
到這裏(SqlSessionDaoSupport.java),一目瞭然,需要執行的sql使用哪個數據源,是通過setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)這個方法指定的。
此時思路就很清晰了,我只需要讓我需要訪問數據庫的方法在setSqlSessionFactory時就根據我的需要設置特定的數據源。
這裏有兩種方式:
1、重寫setSqlSessionFactory讓他按我們需要的方式進行set操作
2、直接重寫getSqlSession,指定我需要返回的sqlSession(sqlSession需要數據源)
第一種有個點需要注意,如果只是繼承SqlSessionDaoSupport.java你會發現想要重寫這個setSqlSessionFactory方法是不行的,因爲這個方法是final的無法被重寫。需要重新定義一個類來替代SqlSessionDaoSupport.java並將setSqlSessionFactory方法定義成非final。
第二種同理,因爲getSqlSession方法被設置成final這裏要重寫 需要修改成非final。
這裏我直接重新定義了一個名爲DynamicDaoSupport的類,該類直接繼承自DaoSupport並實現其中的所有方法,唯一的不同點就是setSqlSessionFactory方法以及getSqlSession方法被定義成了非final的,這樣我在外部調用時就可以重寫這個方法,按我的需要set指定的SqlSessionFactory。
下面是具體實現代碼:
首先我們得定義兩個數據源,並通過org.springframework.beans.factory.config.MethodInvokingFactoryBean
將sqlSessionFactory註冊到指定的類中,這樣我在處理類中就能拿到sqlSessionFactory的bean。這裏通過targetObject指定需要注入到哪個類,通過targetMethod找到對應類的對應方法。
<?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:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- myBatis分頁bean -->
<bean id="paginationInterceptor"
class="com.smartpay.its.boss.framework.repository.mybatis.pagination.interceptor.PaginationInterceptor"></bean>
<!-- sqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 這裏可以省略,採用spring配置方式 <property name="configLocation" value="classpath:mybatis/conf/mybatis-config.xml"
/> -->
<property name="dataSource" ref="dataSource" />
<!-- 自動掃描entity目錄, 省掉Configuration.xml裏的手工配置 -->
<property name="typeAliasesPackage" value="com.smartpay.ops.boss.task" />
<!-- 顯式指定Mapper文件位置 -->
<property name="mapperLocations" value="classpath*:mybatis/bm/**/*Mapper.xml" />
<!-- 這裏進行配置mybatis攔截器插件,在這裏我已經通過註解方式封裝了,因爲個人感覺攔截器的模式不夠靈活,還影響性能 -->
<property name="plugins">
<array>
<ref bean="paginationInterceptor" />
</array>
</property>
<property name="configurationProperties">
<props>
<prop key="dialect">oracle</prop>
</props>
</property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!-- MSC庫 mscdbSqlSessionFactory配置 -->
<bean id="mscdbSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--這裏可以省略,採用spring配置方式 <property name="configLocation" value="classpath:mybatis/conf/mybatis-config.xml"-->
<property name="dataSource" ref="dataSourceMscdb" />
<!--自動掃描entity目錄, 省掉Configuration.xml裏的手工配置-->
<property name="typeAliasesPackage" value="com.smartpay.ops.boss.task" />
<!--顯式指定Mapper文件位置-->
<property name="mapperLocations" value="classpath*:mybatis/mscdb/**/*Mapper.xml" />
<!--這裏進行配置mybatis攔截器插件,在這裏我已經通過註解方式封裝了,因爲個人感覺攔截器的模式不夠靈活,還影響性能-->
<property name="plugins">
<array>
<ref bean="paginationInterceptor" />
</array>
</property>
<property name="configurationProperties">
<props>
<prop key="dialect">oracle</prop>
</props>
</property>
</bean>
<bean id="dynamicSqlSessionDaoSupport" class="com.smartpay.ops.boss.task.util.DynamicSqlSessionDaoSupport" lazy-init="true"></bean>
<!-- 把SqlSessionFactory註冊到SqlSessionDaoSupport中-->
<bean id="orderCreatorRegister" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject">
<ref local="dynamicSqlSessionDaoSupport" />
</property>
<property name="targetMethod">
<value>setDefaultFactory</value>
</property>
<property name="arguments">
<ref bean="mscdbSqlSessionFactory" />
</property>
</bean>
</beans>
我們在DynamicSqlSessionDaoSupport中定義了一個靜態的map用於接收注入的bean
package com.smartpay.ops.boss.task.util;
import static org.springframework.util.Assert.notNull;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.smartpay.ops.boss.task.system.impl.AutoTaskServiceImpl;
/**
* 重寫原始的sqlSession獲取sqlSessionFactory的方式
* @author jugg
*
*/
public class DynamicSqlSessionDaoSupport extends DynamicDaoSupport {
final static Loggerlogger = LoggerFactory.getLogger(AutoTaskServiceImpl.class);
private static Map<String, SqlSessionFactory> targetSqlSessionFactorys=new HashMap<String, SqlSessionFactory>();
private SqlSessionFactory defaultTargetSqlSessionFactory;
private SqlSession sqlSession;
/**
* 通過spring註冊數據源sqlSessionFactory
* @param sqlSessionFactory
*/
public void setDefaultFactory(DefaultSqlSessionFactory sqlSessionFactory) {
defaultTargetSqlSessionFactory=sqlSessionFactory;
if(defaultTargetSqlSessionFactory==null){
logger.info("注入的sqlSessionFactory爲空");
}else{
targetSqlSessionFactorys.put("msc", defaultTargetSqlSessionFactory);
}
}
@Override
public final SqlSession getSqlSession() {
if (targetSqlSessionFactorys != null) {
SqlSessionFactory sessionFactory=targetSqlSessionFactorys.get("msc");
this.sqlSession = SqlSessionUtils.getSqlSession(sessionFactory);
return this.sqlSession;
}else{
logger.error("獲取sqlSessionFactory數據源失敗!");
return null;
}
}
@Override
protected void checkDaoConfig() {
//Assert.notNull(getSqlSession(), "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
}
}
上面的代碼,getSqlSession沒有直接像父類那樣返回this.sqlSession。而是自己通過SqlSessionUtils根據指定的sessionFactory生成出sqlSession。
這裏有一點。
下面是實際調用端的寫法:
在sql需要執行的時候,getSession方法實際已經進入到父類DynamicBaseMybatisDao的getSqlSession。流程圖如下:
測試驗證:
上下兩個findByPKId方法調用兩個daoImpl類,這兩個類的區別就是一個使用原生的AbstractBaseMyBatisDao一個使用我們自己定義的DynamicBaseMybatisDao。原生的訪問了xml中配置的sqlSession,而自定義的類訪問的是我們通過spring注入的另一個數據源mscdbSqlSessionFactory
我們看到調用findById方法查詢出兩個庫裏的結果,結果顯而易見!