spring+mybatis多数据源动态切换

时间:2021-03-08 13:16:05

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