關於Spring3 + Mybatis3整合時,多數據源動態切換的問題

以前的項目經歷中,基本上都是Spring + Hibernate + Spring JDBC這種組合用的多。至於MyBatis,也就這個項目纔開始試用,閒話不多說,進入正題。


以前的這種框架組合中,動態數據源切換可謂已經非常成熟了,網上也有非常多的博客介紹,都是繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()方法。具體做法就不在此廢話了。


所以當項目中碰到這個問題,我幾乎想都沒有想,就採用了這種做法,但是一測試,一點反應都沒有。當時覺得不可能,於是斷點,加log調試,發現determineCurrentLookupKey()根本沒有調用。  


爲什麼列? 這不可能啊。靜下心來,仔細想想,纔想到一個關鍵的問題: Mybatis整合Spring,而不是Spring整合的Mybatis! 直覺告訴我,問題就出在這裏。


於是花時間去研究一下mybatis-spring.jar 這個包,發現有SqlSession這東西,很本能的就注意到了這一塊,然後大致看一下他的一些實現類。於是就發現了他的實現類裏面有一個內部類SqlSessionInterceptor(研究過程就不多說了,畢竟是個痛苦的過程)


好吧,這個類的作用列,就是產生sessionProxy。關鍵代碼如下


      final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

這個sqlSessionFactory 我們就很眼熟啦,是我們在spring配置文件中配了的,是吧,也就是說這東西是直接從我們配置文件中讀進來,但這東西,就關聯了Datasource。所以就想到,如果能把這東西,做到動態,那麼數據源切換,也就動態了。


於是第一反應就是寫了一個類,然後在裏面定義一個Map,用來存放多個SqlSessionFactory,並採用Setter方法進行屬性注入。

public class EjsSqlSessionTemplate extends SqlSessionTemplate {

    private Map<String, SqlSessionFactory> targetSqlSessionFactory = new HashMap<String, SqlSessionFactory>();
    public void setTargetSqlSessionFactory(Map<String, SqlSessionFactory> targetSqlSessionFactory) {
        this.targetSqlSessionFactory = targetSqlSessionFactory;
    }

所以Spring的配置文件就變成了這樣:

 <bean id="sqlSessionTemplate" class="com.ejushang.spider.datasource.EjsSqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory" />
        <property name="targetSqlSessionFactory">
            <map>
                <entry value-ref="sqlSessionFactory" key="spider"/>
                <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>
            </map>
        </property>
    </bean>

    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.foo.bar.**.mapper*" />
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
    </bean>


那麼這個思想是那裏來的列? 當然就是借鑑了Spring的動態數據源的思想啦,對比一下Spring動態數據源的配置,看看是不是差不多?

然後重寫了個關鍵的方法:

/**
     * 重寫得到SqlSessionFactory的方法
     * @return
     */
    @Override
    public SqlSessionFactory getSqlSessionFactory() {

        SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());
        if (targetSqlSessionFactory != null) {
            return targetSqlSessionFactory;
        } else if ( this.getSqlSessionFactory() != null) {
            return  this.getSqlSessionFactory();
        }
        throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");
    }


而SqlSessionContextHolder就很簡單,就是一個ThreadLocal的思想

public class SqlSessionContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    private static Logger logger = LoggerFactory.getLogger(SqlSessionContextHolder.class);

    public static void setSessionFactoryKey(String dataSourceKey) {
        contextHolder.set(dataSourceKey);
    }

    public static String getDataSourceKey() {
        String key = contextHolder.get();
        logger.info("當前線程Thread:"+Thread.currentThread().getName()+" 當前的數據源 key is "+ key);
        return key;
    }

}

博主信心滿滿就開始測試了。。結果發現不行,切換不過來,始終都是綁定的是構造函數中的那個默認的sqlSessionFactory,當時因爲看了一天源碼,頭也有點暈。其實爲什麼列?

看看我們產生sessionProxy的地方代碼,他的sqlSessionFactory是直接從構造函數來拿的。而構造函數中的sqlSessionFactory在spring容器啓動時,就已經初始化好了,這點也可以從我們Spring配置文件中得到證實。


那這個問題,怎麼解決列? 於是博主便想重寫那個sqlSessionInterceptor。 擦,問題就來了,這個類是private的,沒辦法重寫啊。於是博主又只能在自己的EjsSqlSessionTemplate類中,也定義了一個內部類,把源碼中的代碼都copy過來,唯一不同的就是我不是讀取構造函數中的sqlSessionFactory.而是每次都去調用 getSqlSessionFactory()方法。代碼如下:

 final SqlSession sqlSession = getSqlSession(
                    EjsSqlSessionTemplate.this.getSqlSessionFactory(),
                    EjsSqlSessionTemplate.this.getExecutorType(),
                    EjsSqlSessionTemplate.this.getPersistenceExceptionTranslator());

再試,發現還是不行,再找原因,又迴歸到了剛纔那個問題。因爲我沒有重寫SqlSessionTemplate的構造函數,而sqlSessionProxy是在構函數中初始化的,代碼如下:

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

而SqlSessionInterceptor()這東西都是private。 所以父類壓根就不會加載我寫的那個SqlSessionInterceptor()。所以問題就出在這,那好吧,博主又重寫構函數

  public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        super(getSqlSessionFactory(), executorType, exceptionTranslator);
    }

很明顯這段代碼是編譯不通過的,構造函數中,怎麼可能調用類實例方法列?  那怎麼辦列? 又只有把父類的構造函數copy過來,那問題又有了,這些成員屬性又沒有。那又只得把他們也搬過來。。  後來,這個動態數據數據源的功能,終於完成了。 

--------------------------------------------------------------------------------------------------------------------分割線-----------------------------------------------------------------------------------------------------------整個完整的代碼如下:


1、重寫SqlSessionTemplate (重寫的過程已經在上面分析過了)

public class EjsSqlSessionTemplate extends SqlSessionTemplate {

    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    private Map<Object, SqlSessionFactory> targetSqlSessionFactory;

    public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
        this.targetSqlSessionFactory = targetSqlSessionFactory;
    }


    public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
                .getEnvironment().getDataSource(), true));
    }

    public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                    PersistenceExceptionTranslator exceptionTranslator) {

        super(sqlSessionFactory, executorType, exceptionTranslator);

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;

        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[] { SqlSession.class },
                new SqlSessionInterceptor());

    }



    @Override
    public SqlSessionFactory getSqlSessionFactory() {

        SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());
        if (targetSqlSessionFactory != null) {
            return targetSqlSessionFactory;
        } else if ( this.sqlSessionFactory != null) {
            return  this.sqlSessionFactory;
        }
       throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");
    }

    @Override
    public Configuration getConfiguration() {
        return this.getSqlSessionFactory().getConfiguration();
    }

    public ExecutorType getExecutorType() {
        return this.executorType;
    }

    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.<T> selectOne(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.<T> selectOne(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.<E> selectList(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.<E> selectList(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, handler);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement) {
        return this.sqlSessionProxy.update(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement) {
        return this.sqlSessionProxy.delete(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }

    /**
     * {@inheritDoc}
     */
    public void commit() {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void commit(boolean force) {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback() {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void close() {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void clearCache() {
        this.sqlSessionProxy.clearCache();
    }

    /**
     * {@inheritDoc}
     */
    public Connection getConnection() {
        return this.sqlSessionProxy.getConnection();
    }

    /**
     * {@inheritDoc}
     * @since 1.0.2
     */
    public List<BatchResult> flushStatements() {
        return this.sqlSessionProxy.flushStatements();
    }

    /**
     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
     * the {@code PersistenceExceptionTranslator}.
     */
    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = getSqlSession(
                    EjsSqlSessionTemplate.this.getSqlSessionFactory(),
                    EjsSqlSessionTemplate.this.executorType,
                    EjsSqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory())) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (EjsSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    Throwable translated = EjsSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                closeSqlSession(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
}


2。自定義了一個註解

/**
 * 註解式數據源,用來進行數據源切換
 * User:Amos.zhou
 * Date: 14-2-27
 * Time: 下午2:34
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChooseDataSource {

    String value() default "";
}


3.定義一個AspectJ的切面(我習慣用AspectJ,因爲spring AOP不支持cflow()這些語法),所以在編譯,打包的時候一定要用aspectJ的編譯器,不能直接用原生的JDK。有些方法就是我基於以前Hibernate,JDBC動態數據源的時候改動的。
/**
 * <li>類描述:完成數據源的切換,抽類切面,具體項目繼承一下,不需要重寫即可使用</li>
 *
 * @author: amos.zhou
 * 2013-8-1 上午11:51:40
 * @since v1.0
 */
@Aspect
public abstract class ChooseDataSourceAspect {

    protected static final ThreadLocal<String> preDatasourceHolder = new ThreadLocal<String>();

    @Pointcut("execution(public * *.*(..))")
    public void allMethodPoint() {

    }

    @Pointcut("@within(com.ejushang.spider.annotation.ChooseDataSource) && allMethodPoint()")
    public void allServiceMethod() {

    }


    /**
     * 對所有註解有ChooseDataSource的類進行攔截
     */
    @Pointcut("cflow(allServiceMethod()) && allServiceMethod()")
    public void changeDatasourcePoint() {
    }


    /**
     * 根據@ChooseDataSource的屬性值設置不同的dataSourceKey,以供DynamicDataSource
     */
    @Before("changeDatasourcePoint()")
    public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {
        //拿到anotation中配置的數據源
        String resultDS = determineDatasource(jp);
        //沒有配置實用默認數據源
        if (resultDS == null) {
            SqlSessionContextHolder.setSessionFactoryKey(null);
            return;
        }
        preDatasourceHolder.set(SqlSessionContextHolder.getDataSourceKey());
        //將數據源設置到數據源持有者
        SqlSessionContextHolder.setSessionFactoryKey(resultDS);

    }

    /**
     * <p>創建時間: 2013-8-20 上午9:48:44</p>
     * 如果需要修改獲取數據源的邏輯,請重寫此方法
     *
     * @param jp
     * @return
     */
    @SuppressWarnings("rawtypes")
    protected String determineDatasource(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Class targetClass = jp.getSignature().getDeclaringType();
        String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);
        String dataSourceForTargetMethod = resolveDataSourceFromMethod(
                targetClass, methodName);
        String resultDS = determinateDataSource(dataSourceForTargetClass,
                dataSourceForTargetMethod);
        return resultDS;
    }


    /**
     * 方法執行完畢以後,數據源切換回之前的數據源。
     * 比如foo()方法裏面調用bar(),但是bar()另外一個數據源,
     * bar()執行時,切換到自己數據源,執行完以後,要切換到foo()所需要的數據源,以供
     * foo()繼續執行。
     * <p>創建時間: 2013-8-16 下午4:27:06</p>
     */
    @After("changeDatasourcePoint()")
    public void restoreDataSourceAfterMethodExecution() {
        SqlSessionContextHolder.setSessionFactoryKey(preDatasourceHolder.get());
        preDatasourceHolder.remove();
    }


    /**
     * <li>創建時間: 2013-6-17 下午5:34:13</li> <li>創建人:amos.zhou</li> <li>方法描述 :</li>
     *
     * @param targetClass
     * @param methodName
     * @return
     */
    @SuppressWarnings("rawtypes")
    private String resolveDataSourceFromMethod(Class targetClass,
                                               String methodName) {
        Method m = ReflectUtil.findUniqueMethod(targetClass, methodName);
        if (m != null) {
            ChooseDataSource choDs = m.getAnnotation(ChooseDataSource.class);
            return resolveDataSourcename(choDs);
        }
        return null;
    }

    /**
     * <li>創建時間: 2013-6-17 下午5:06:02</li>
     * <li>創建人:amos.zhou</li>
     * <li>方法描述 : 確定
     * 最終數據源,如果方法上設置有數據源,則以方法上的爲準,如果方法上沒有設置,則以類上的爲準,如果類上沒有設置,則使用默認數據源</li>
     *
     * @param classDS
     * @param methodDS
     * @return
     */
    private String determinateDataSource(String classDS, String methodDS) {
//        if (null == classDS && null == methodDS) {
//            return null;
//        }
        // 兩者必有一個不爲null,如果兩者都爲Null,也會返回Null
        return methodDS == null ? classDS : methodDS;
    }

    /**
     * <li>創建時間: 2013-6-17 下午4:33:03</li> <li>創建人:amos.zhou</li> <li>方法描述 : 類級別的 @ChooseDataSource
     * 的解析</li>
     *
     * @param targetClass
     * @return
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private String resolveDataSourceFromClass(Class targetClass) {
        ChooseDataSource classAnnotation = (ChooseDataSource) targetClass
                .getAnnotation(ChooseDataSource.class);
        // 直接爲整個類進行設置
        return null != classAnnotation ? resolveDataSourcename(classAnnotation)
                : null;
    }

    /**
     * <li>創建時間: 2013-6-17 下午4:31:42</li> <li>創建人:amos.zhou</li> <li>方法描述 :
     * 組裝DataSource的名字</li>
     *
     * @param ds
     * @return
     */
    private String resolveDataSourcename(ChooseDataSource ds) {
        return ds == null ? null : ds.value();
    }

}

那麼以上3個類,就可以作爲一個公共的組件打個包了。

那麼項目中具體 怎麼用列?

4.  在項目中定義一個具體的AspectJ切面

@Aspect
public class OrderFetchAspect extends ChooseDataSourceAspect {
}

如果你的根據你的需要重寫方法,我這邊是不需要重寫的,所以空切面就行了。


5.配置spring,在上面的分析過程中已經貼出了,基本上就是每個數據庫,一個dataSource,每個DataSource一個SqlSessionFactory。最後配一個SqlSessionTemplate,也就是我們自己重寫的。再就是MapperScan了,大致如下(數據庫連接信息已去除,包名爲杜撰):

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        
    </bean>

    <bean id="dataSourceTb" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        
    </bean>

    <!-- 事務管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 註解控制事務 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml" />
        <property name="mapperLocations" value="classpath*:com/foo/bar/**/config/*mapper.xml" />
    </bean>

    <bean id="sqlSessionFactoryTb" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSourceTb"/>
        <property name="configLocation" value="classpath:mybatis.xml" />
        <property name="mapperLocations" value="classpath*:com/foo/bar/**/configtb/*mapper.xml" />
    </bean>

    <bean id="sqlSessionTemplate" class="com.foo.bar.template.EjsSqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory" />
        <property name="targetSqlSessionFactory">
            <map>
                <entry value-ref="sqlSessionFactory" key="spider"/>
                <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>
            </map>
        </property>
    </bean>

    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.foo.bar.**.mapper*" />
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
    </bean>


6.具體應用

@ChooseDataSource("spider")
public class ShopServiceTest extends ErpTest {

    private static final Logger log = LoggerFactory.getLogger(ShopServiceTest.class);

    @Autowired
    private IShopService shopService;

    @Autowired
    private IJdpTbTradeService jdpTbTradeService;


    @Test
    @Rollback(false)
    public void testFindAllShop(){
        List<Shop> shopList1 = shopService.findAllShop();
        for(Shop shop : shopList1){
            System.out.println(shop);
        }

        fromTestDB();
    }

    @ChooseDataSource("sysinfo")
    private void fromTestDB(){
        List<Shop> shopList = jdpTbTradeService.findAllShop();
        for(Shop shop : shopList){
            System.out.println(shop);
        }
    }
}

測試發現 shopList1是從spider庫查出來的數據,而fromDB則是從sysinfo中查出來的數據。 那麼我們就大功告成。 

要做到我以上功能,Spring AOP是做不到的,因爲他不支持Cflow(),這也就是我爲什麼要用AspectJ的原因。



-----------------------------------------------------------------------------------------------再次分割線-------------------------------------------------------------------------------------------------------------------

好了,功能我們已經實現了,你有沒有覺得很麻煩,這一點也不Spring的風格,Spring的各個地方擴展都是很方便的。那麼我們看看,在SqlSessionTemplate中的什麼地方改動一下,我們就可以很輕鬆的實現這個功能列?大家可以理解了,再回去看一下源碼。


其實,只要將源碼中的那個SqlSessionInterceptor的這句話:

 final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

改爲:
 final SqlSession sqlSession = getSqlSession(
                    EjsSqlSessionTemplate.this.getSqlSessionFactory(),
                    EjsSqlSessionTemplate.this.executorType,
                    EjsSqlSessionTemplate.this.exceptionTranslator);

保證 每次在產生Session代理的時候,傳進去的參數都是調用getSqlSessionFactory()獲取,那麼我們自定義的SqlSessionTemplate,只要重寫getSqlSessionFactory(),加多一個以下2句話:

 private Map<Object, SqlSessionFactory> targetSqlSessionFactory;

    public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
        this.targetSqlSessionFactory = targetSqlSessionFactory;
    }

那麼就完全可以實現動態數據源切換。  那麼mybatis-spring的項目團隊會這樣維護麼? 我會以mail的方式與他們溝通。至於能否改進,我們不得而知了。


其實這也就引發一個關於面向對象設計時的思想,也是一直爭論得喋喋不休的一個問題:

    在類的方法中,如果要用到類的屬性時,是直接用this.filedName  來操作,還是用  getFiledName() 來進行操作?

其實以前我也是偏向於直接用this.屬性來進行操作的,但是經歷過這次以後,我想我會偏向於後者。


發佈了110 篇原創文章 · 獲贊 22 · 訪問量 63萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章