spring mvc+mybatis+多數據源切換 選取oracle,mysql作為例子切換數據源。oracle為默認數據源,在測試的action中,進行mysql和oracle的動態切換。
web.xml
<context-param> <param-name>webAppRootKey</param-name> <param-value>trac</param-value> </context-param> <!-- Spring的log4j監聽器 --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <!-- 字符集 過濾器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Spring view分發器 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcher.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
applicationContext.xml
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> </bean> <bean id="mySqlDataSource" parent="parentDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/test"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="oracleDataSource" parent="parentDataSource"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property> <property name="url" value="jdbc:oracle:thin:@10.16.17.40:1531:addb"></property> <property name="username" value="trac"></property> <property name="password" value="trac"></property> </bean> <bean id="dataSource" class="com.trac.dao.datasource.DataSources"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry value-ref="mySqlDataSource" key="MYSQL"></entry> <entry value-ref="oracleDataSource" key="ORACLE"></entry> </map> </property> <property name="defaultTargetDataSource" ref="oracleDataSource"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 創建SqlSessionFactory,同時指定數據源和mapper --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath*:com/trac/ibatis/dbcp/*.xml" /> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.trac.dao" /> </bean>
配置 parentDataSource 的父bean.再配置多個數據源繼承這個父bean,對driverClass,url,username,password,等數據源連接參數進行各自的重寫。例如 mySqlDataSource ,在 DataSources bean中注入所有要切換的數據源,並且設置默認的數據源。
DataSourceInstances.java
public class DataSourceInstances{ public static final String MYSQL="MYSQL"; public static final String ORACLE="ORACLE"; }
DataSourceSwitch.java
public class DataSourceSwitch{ private static final ThreadLocal contextHolder=new ThreadLocal(); public static void setDataSourceType(String dataSourceType){ contextHolder.set(dataSourceType); } public static String getDataSourceType(){ return (String) contextHolder.get(); } public static void clearDataSourceType(){ contextHolder.remove(); } }
DataSources.java
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DataSources extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { return DataSourceSwitch.getDataSourceType(); } }
測試
@Controller @SuppressWarnings("unused") public class TestAction { @Autowired TestMapper testMapper; @RequestMapping("/test.action") public ModelAndView test( HttpServletRequest request, HttpServletResponse resp){ ModelAndView model = new ModelAndView("test"); model.addObject("test1", "這是一個測試,獲取默認數據連接MYSQL:"+testMapper.test()); DataSourceSwitch.setDataSourceType(DataSourceInstances.ORACLE); model.addObject("test2", "這是一個測試,獲取數據連接ORACLE:"+testMapper.test()); DataSourceSwitch.setDataSourceType(DataSourceInstances.MYSQL); model.addObject("test3", "這是一個測試,獲取數據連接MYSQL:"+testMapper.test()); return model; } }
代碼解釋:
查看AbstractRoutingDataSource中的獲取數據庫連接源碼
public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); }
查看determineTargetDataSource方法
protected DataSource determineTargetDataSource() { Assert.notNull(resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey); if(dataSource == null && (lenientFallback || lookupKey == null)) dataSource = resolvedDefaultDataSource; if(dataSource == null) throw new IllegalStateException((new StringBuilder()).append("Cannot determine target DataSource for lookup key [").append(lookupKey).append("]").toString()); else return dataSource; }
其中DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey); 中的resolvedDataSources 就是我們spring中設置的targetDataSources,是一個Map類型,里面有我們設置的MYSQL和ORACLE數據庫連接池
注意determineCurrentLookupKey方法,
protected abstract Object determineCurrentLookupKey();
是一個抽象方法,需要我們去實現,我們將數據源對應的KEY放在本地線程中,那么可以隨時在代碼中進行切換數據源
默認數據源
在spring配置文件中,我們將defaultTargetDataSource注入到AbstractRoutingDataSource中
public void afterPropertiesSet() { if(targetDataSources == null) throw new IllegalArgumentException("Property 'targetDataSources' is required"); resolvedDataSources = new HashMap(targetDataSources.size()); Object lookupKey; DataSource dataSource; for(Iterator iterator = targetDataSources.entrySet().iterator(); iterator.hasNext(); resolvedDataSources.put(lookupKey, dataSource)) { java.util.Map.Entry entry = (java.util.Map.Entry)iterator.next(); lookupKey = resolveSpecifiedLookupKey(entry.getKey()); dataSource = resolveSpecifiedDataSource(entry.getValue()); } if(defaultTargetDataSource != null) resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource); }
AbstractRoutingDataSource類實現了InitializingBean接口,項目啟動會實現方法afterPropertiesSet,生成resolvedDefaultDataSource實例,這樣在determineTargetDataSource方法中如果獲取本地線程變量中的連接位空,那么就選擇默認數據源。