關(guān)于Spring3 + Mybatis3整合時多數(shù)據(jù)源動態(tài)切換的問題
以前的項目經(jīng)歷中,基本上都是spring + hibernate + Spring JDBC這種組合用的多。至于MyBatis,也就這個項目才開始試用,閑話不多說,進入正題。
以前的這種框架組合中,動態(tài)數(shù)據(jù)源切換可謂已經(jīng)非常成熟了,網(wǎng)上也有非常多的博客介紹,都是繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()方法。具體做法就不在此廢話了。
所以當項目中碰到這個問題,我?guī)缀跸攵紱]有想,就采用了這種做法,但是一測試,一點反應(yīng)都沒有。當時覺得不可能,于是斷點,加log調(diào)試,發(fā)現(xiàn)determineCurrentLookupKey()根本沒有調(diào)用。
為什么列? 這不可能啊。靜下心來,仔細想想,才想到一個關(guān)鍵的問題: Mybatis整合Spring,而不是Spring整合的Mybatis! 直覺告訴我,問題就出在這里。
于是花時間去研究一下mybatis-spring.jar 這個包,發(fā)現(xiàn)有SqlSession這東西,很本能的就注意到了這一塊,然后大致看一下他的一些實現(xiàn)類。于是就發(fā)現(xiàn)了他的實現(xiàn)類里面有一個內(nèi)部類SqlSessionInterceptor(研究過程就不多說了,畢竟是個痛苦的過程)
好吧,這個類的作用列,就是產(chǎn)生sessionProxy。關(guān)鍵代碼如下
final SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
這個sqlSessionFactory 我們就很眼熟啦,是我們在spring配置文件中配了的,是吧,也就是說這東西是直接從我們配置文件中讀進來,但這東西,就關(guān)聯(lián)了Datasource。所以就想到,如果能把這東西,做到動態(tài),那么數(shù)據(jù)源切換,也就動態(tài)了。
于是第一反應(yīng)就是寫了一個類,然后在里面定義一個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的動態(tài)數(shù)據(jù)源的思想啦,對比一下Spring動態(tài)數(shù)據(jù)源的配置,看看是不是差不多?
然后重寫了個關(guān)鍵的方法:
/**
* 重寫得到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()+" 當前的數(shù)據(jù)源 key is "+ key);
return key;
}
}
博主信心滿滿就開始測試了。。結(jié)果發(fā)現(xiàn)不行,切換不過來,始終都是綁定的是構(gòu)造函數(shù)中的那個默認的sqlSessionFactory,當時因為看了一天源碼,頭也有點暈。其實為什么列?
看看我們產(chǎn)生sessionProxy的地方代碼,他的sqlSessionFactory是直接從構(gòu)造函數(shù)來拿的。而構(gòu)造函數(shù)中的sqlSessionFactory在spring容器啟動時,就已經(jīng)初始化好了,這點也可以從我們Spring配置文件中得到證實。
那這個問題,怎么解決列? 于是博主便想重寫那個sqlSessionInterceptor。 擦,問題就來了,這個類是private的,沒辦法重寫啊。于是博主又只能在自己的EjsSqlSessionTemplate類中,也定義了一個內(nèi)部類,把源碼中的代碼都copy過來,唯一不同的就是我不是讀取構(gòu)造函數(shù)中的sqlSessionFactory.而是每次都去調(diào)用 getSqlSessionFactory()方法。代碼如下:
final SqlSession sqlSession = getSqlSession( EjsSqlSessionTemplate.this.getSqlSessionFactory(), EjsSqlSessionTemplate.this.getExecutorType(), EjsSqlSessionTemplate.this.getPersistenceExceptionTranslator());
再試,發(fā)現(xiàn)還是不行,再找原因,又回歸到了剛才那個問題。因為我沒有重寫SqlSessionTemplate的構(gòu)造函數(shù),而sqlSessionProxy是在構(gòu)函數(shù)中初始化的,代碼如下:
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()。所以問題就出在這,那好吧,博主又重寫構(gòu)函數(shù)
public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
super(getSqlSessionFactory(), executorType, exceptionTranslator);
}
很明顯這段代碼是編譯不通過的,構(gòu)造函數(shù)中,怎么可能調(diào)用類實例方法列? 那怎么辦列? 又只有把父類的構(gòu)造函數(shù)copy過來,那問題又有了,這些成員屬性又沒有。那又只得把他們也搬過來。。 后來,這個動態(tài)數(shù)據(jù)數(shù)據(jù)源的功能,終于完成了。
--------------------------------------------------------------------------------------------------------------------分割線-----------------------------------------------------------------------------------------------------------整個完整的代碼如下:
1、重寫SqlSessionTemplate (重寫的過程已經(jīng)在上面分析過了)
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。自定義了一個注解
/**
* 注解式數(shù)據(jù)源,用來進行數(shù)據(jù)源切換
* 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動態(tài)數(shù)據(jù)源的時候改動的。
/**
* <li>類描述:完成數(shù)據(jù)源的切換,抽類切面,具體項目繼承一下,不需要重寫即可使用</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() {
}
/**
* 根據(jù)@ChooseDataSource的屬性值設(shè)置不同的dataSourceKey,以供DynamicDataSource
*/
@Before("changeDatasourcePoint()")
public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {
//拿到anotation中配置的數(shù)據(jù)源
String resultDS = determineDatasource(jp);
//沒有配置實用默認數(shù)據(jù)源
if (resultDS == null) {
SqlSessionContextHolder.setSessionFactoryKey(null);
return;
}
preDatasourceHolder.set(SqlSessionContextHolder.getDataSourceKey());
//將數(shù)據(jù)源設(shè)置到數(shù)據(jù)源持有者
SqlSessionContextHolder.setSessionFactoryKey(resultDS);
}
/**
* <p>創(chuàng)建時間: 2013-8-20 上午9:48:44</p>
* 如果需要修改獲取數(shù)據(jù)源的邏輯,請重寫此方法
*
* @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;
}
/**
* 方法執(zhí)行完畢以后,數(shù)據(jù)源切換回之前的數(shù)據(jù)源。
* 比如foo()方法里面調(diào)用bar(),但是bar()另外一個數(shù)據(jù)源,
* bar()執(zhí)行時,切換到自己數(shù)據(jù)源,執(zhí)行完以后,要切換到foo()所需要的數(shù)據(jù)源,以供
* foo()繼續(xù)執(zhí)行。
* <p>創(chuàng)建時間: 2013-8-16 下午4:27:06</p>
*/
@After("changeDatasourcePoint()")
public void restoreDataSourceAfterMethodExecution() {
SqlSessionContextHolder.setSessionFactoryKey(preDatasourceHolder.get());
preDatasourceHolder.remove();
}
/**
* <li>創(chuàng)建時間: 2013-6-17 下午5:34:13</li> <li>創(chuàng)建人: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>創(chuàng)建時間: 2013-6-17 下午5:06:02</li>
* <li>創(chuàng)建人:amos.zhou</li>
* <li>方法描述 : 確定
* 最終數(shù)據(jù)源,如果方法上設(shè)置有數(shù)據(jù)源,則以方法上的為準,如果方法上沒有設(shè)置,則以類上的為準,如果類上沒有設(shè)置,則使用默認數(shù)據(jù)源</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>創(chuàng)建時間: 2013-6-17 下午4:33:03</li> <li>創(chuàng)建人:amos.zhou</li> <li>方法描述 : 類級別的 @ChooseDataSource
* 的解析</li>
*
* @param targetClass
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private String resolveDataSourceFromClass(Class targetClass) {
ChooseDataSource classAnnotation = (ChooseDataSource) targetClass
.getAnnotation(ChooseDataSource.class);
// 直接為整個類進行設(shè)置
return null != classAnnotation ? resolveDataSourcename(classAnnotation)
: null;
}
/**
* <li>創(chuàng)建時間: 2013-6-17 下午4:31:42</li> <li>創(chuàng)建人: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 {
}
如果你的根據(jù)你的需要重寫方法,我這邊是不需要重寫的,所以空切面就行了。
5.配置spring,在上面的分析過程中已經(jīng)貼出了,基本上就是每個數(shù)據(jù)庫,一個dataSource,每個DataSource一個SqlSessionFactory。最后配一個SqlSessionTemplate,也就是我們自己重寫的。再就是MapperScan了,大致如下(數(shù)據(jù)庫連接信息已去除,包名為杜撰):
<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> <!-- 事務(wù)管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 注解控制事務(wù) --> <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*:<span style="font-family: Arial, Helvetica, sans-serif;">com/foo/bar</span><span style="font-family: Arial, Helvetica, sans-serif;">/**/configtb/*mapper.xml" /></span> </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.具體應(yīng)用
@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);
}
}
}
測試發(fā)現(xiàn) shopList1是從spider庫查出來的數(shù)據(jù),而fromDB則是從sysinfo中查出來的數(shù)據(jù)。 那么我們就大功告成。
要做到我以上功能,Spring AOP是做不到的,因為他不支持Cflow(),這也就是我為什么要用AspectJ的原因。
-----------------------------------------------------------------------------------------------再次分割線-------------------------------------------------------------------------------------------------------------------
好了,功能我們已經(jīng)實現(xiàn)了,你有沒有覺得很麻煩,這一點也不Spring的風格,Spring的各個地方擴展都是很方便的。那么我們看看,在SqlSessionTemplate中的什么地方改動一下,我們就可以很輕松的實現(xiàn)這個功能列?大家可以理解了,再回去看一下源碼。
其實,只要將源碼中的那個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);
保證 每次在產(chǎn)生Session代理的時候,傳進去的參數(shù)都是調(diào)用getSqlSessionFactory()獲取,那么我們自定義的SqlSessionTemplate,只要重寫getSqlSessionFactory(),加多一個以下2句話:
private Map<Object, SqlSessionFactory> targetSqlSessionFactory;
public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
this.targetSqlSessionFactory = targetSqlSessionFactory;
}
那么就完全可以實現(xiàn)動態(tài)數(shù)據(jù)源切換。 那么mybatis-spring的項目團隊會這樣維護么? 我會以mail的方式與他們溝通。至于能否改進,我們不得而知了。
其實這也就引發(fā)一個關(guān)于面向?qū)ο笤O(shè)計時的思想,也是一直爭論得喋喋不休的一個問題:
在類的方法中,如果要用到類的屬性時,是直接用this.filedName 來操作,還是用 getFiledName() 來進行操作?
其實以前我也是偏向于直接用this.屬性來進行操作的,但是經(jīng)歷過這次以后,我想我會偏向于后者。
以上所述是小編給大家介紹的關(guān)于Spring3 + Mybatis3整合時多數(shù)據(jù)源動態(tài)切換的問題,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- Spring+Mybatis動態(tài)切換數(shù)據(jù)源的方法
- Spring與Mybatis相結(jié)合實現(xiàn)多數(shù)據(jù)源切換功能
- spring boot+mybatis 多數(shù)據(jù)源切換(實例講解)
- Spring + Mybatis 項目實現(xiàn)動態(tài)切換數(shù)據(jù)源實例詳解
- SpringMVC Mybatis配置多個數(shù)據(jù)源并切換代碼詳解
- spring boot + mybatis實現(xiàn)動態(tài)切換數(shù)據(jù)源實例代碼
- SpringBoot Mybatis動態(tài)數(shù)據(jù)源切換方案實現(xiàn)過程
- Mybatis多數(shù)據(jù)源切換實現(xiàn)代碼
- Spring AOP如何實現(xiàn)注解式的Mybatis多數(shù)據(jù)源切換詳解
- mybatis多數(shù)據(jù)源動態(tài)切換的完整步驟
相關(guān)文章
Hibernate實現(xiàn)批量添加數(shù)據(jù)的方法
這篇文章主要介紹了Hibernate實現(xiàn)批量添加數(shù)據(jù)的方法,詳細分析了基于Hibernate執(zhí)行批量添加操作的具體步驟與相關(guān)實現(xiàn)代碼,需要的朋友可以參考下2016-03-03
Java?數(shù)據(jù)結(jié)構(gòu)與算法系列精講之數(shù)組
數(shù)組是有序的元素序列,若將有限個類型相同的變量的集合命名,那么這個名稱為數(shù)組名。組成數(shù)組的各個變量稱為數(shù)組的分量,也稱為數(shù)組的元素,有時也稱為下標變量。數(shù)組是在程序設(shè)計中,為了處理方便, 把具有相同類型的若干元素按有序的形式組織起來的一種形式2022-02-02
IDEA自帶Maven插件找不到settings.xml配置文件
IDEA自帶了Maven插件,最近發(fā)現(xiàn)了一個問題,IDEA自帶Maven插件找不到settings.xml配置文件,本文就來詳細的介紹一下解決方法,感興趣的可以了解一下2023-11-11
springcloud使用Hystrix進行微服務(wù)降級管理
這篇文章主要介紹了springcloud使用Hystrix進行微服務(wù)降級管理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04
SpringCloud?openfeign聲明式服務(wù)調(diào)用實現(xiàn)方法介紹
在springcloud中,openfeign是取代了feign作為負載均衡組件的,feign最早是netflix提供的,他是一個輕量級的支持RESTful的http服務(wù)調(diào)用框架,內(nèi)置了ribbon,而ribbon可以提供負載均衡機制,因此feign可以作為一個負載均衡的遠程服務(wù)調(diào)用框架使用2022-12-12

