spring+mybatis多数据源动态切换
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方法中如果获取本地线程变量中的连接位空,那么就选择默认数据源。