第一种方法:创建两个会话工厂
配置如下
这种方法,在程序中只需要切换sqlSessionTemplate即可,但是这样第二数据源无事务,
容易抛出TransactionSynchronizationManager.unbindResourceIfPossible异常,这也是其缺点
是否可以再添加一个事务管理器? 下载
你可以试试,我测试没问题。
jdbc.properties配置文件
第二种方法:扩展数据源路由 下载
查看AbstractRoutingDataSource
//抽象数据源路由
从分析AbstractRoutingDataSource获取数据源得出,想要实现多数据源,只需要扩展AbstractRoutingDataSource,并实现determineCurrentLookupKey方法即可,并在determineCurrentLookupKey方法中切换数据源名即可。
下面实验: 下载
//数据源路由
}
//这里我们创建一个数据源上下文句柄,以便切换数据源 下载
//配置如下 下载
手动测试 下载
访问http://localhost:8080/r/test/db.do控制台输出
2016-09-21 17:57:13 -40921 [com.controller.test.TestController] INFO - =============localDao size:5,dataSource
2016-09-21 17:57:13 -40941 [com.controller.test.TestController] INFO - =============syncDao size:4,syncDataSource
上面的方式是手动切换数据源,下面我们通过Spring AOP实现动态切换数据源:
定义注解 下载
定义AOP
配置如下
- <beans>
- <!-- 数据源1 -->
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
- <!-- 数据库基本信息配置 -->
- <property name="url" value="${url}" />
- <property name="username" value="${username}" />
- <property name="password" value="${password}" />
- <property name="driverClassName" value="${driverClassName}" />
- <property name="filters" value="${filters}" />
- <!-- 最大并发连接数 -->
- <property name="maxActive" value="${maxActive}" />
- <!-- 初始化连接数量 -->
- <property name="initialSize" value="${initialSize}" />
- <!-- 配置获取连接等待超时的时间 -->
- <property name="maxWait" value="${maxWait}" />
- <!-- 最小空闲连接数 -->
- <property name="minIdle" value="${minIdle}" />
- </bean>
- <!-- 数据源2 -->
- <bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
- <property name="url" value="${sync.url}" />
- <property name="username" value="${sync.username}" />
- <property name="password" value="${sync.password}" />
- <property name="driverClassName" value="${sync.driverClassName}" />
- <property name="filters" value="${filters}" />
- <property name="maxActive" value="${maxActive}" />
- <property name="initialSize" value="${initialSize}" />>
- <property name="maxWait" value="${maxWait}" />
- <property name="minIdle" value="${minIdle}" />
- </bean>
- <!-- 第一个sqlSessionFactory -->
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
- <!-- mapper扫描 -->
- <property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
- </bean>
- <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
- <constructor-arg ref="sqlSessionFactory" />
- </bean>
- <!-- 第二个sqlSessionFactory -->
- <bean id="syncSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="syncDataSource" />
- <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
- <property name="mapperLocations" value="classpath:mybatis/sync/*.xml"></property>
- </bean>
- <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- <bean id="syncSqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
- <constructor-arg ref="syncSqlSessionFactory" />
- </bean>
- <!-- <aop:aspectj-autoproxy proxy-target-class="true" /> -->
- </beans>
这种方法,在程序中只需要切换sqlSessionTemplate即可,但是这样第二数据源无事务,
容易抛出TransactionSynchronizationManager.unbindResourceIfPossible异常,这也是其缺点
是否可以再添加一个事务管理器? 下载
- <bean name="syncTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="syncDataSource"></property>
- </bean>
你可以试试,我测试没问题。
jdbc.properties配置文件
- url=jdbc\:mysql\://localhost\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8
- driverClassName=com.mysql.jdbc.Driver
- username=donald
- password=123456
- #sync datasource
- sync.url=jdbc\:mysql\://192.168.32.128\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8
- sync.driverClassName=com.mysql.jdbc.Driver
- sync.username=donald
- sync.password=123456
第二种方法:扩展数据源路由 下载
查看AbstractRoutingDataSource
//抽象数据源路由
- public abstract class AbstractRoutingDataSource extends AbstractDataSource
- implements InitializingBean
- {
- public void afterPropertiesSet()
- {
- if(targetDataSources == null)
- throw new IllegalArgumentException("Property 'targetDataSources' is required");
- resolvedDataSources = new HashMap(targetDataSources.size());
- Object lookupKey;
- DataSource dataSource;
- //将配置的多数据源添加到resolvedDataSources中
- 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);
- }
- //数据源key
- protected Object resolveSpecifiedLookupKey(Object lookupKey)
- {
- return lookupKey;
- }
- //获取数据源根据dataSource
- protected DataSource resolveSpecifiedDataSource(Object dataSource)
- throws IllegalArgumentException
- {
- if(dataSource instanceof DataSource)
- return (DataSource)dataSource;
- if(dataSource instanceof String)
- //从bean容器中获取对应的数据源,(DataSource)beanFactory.getBean(dataSourceName, javax/sql/DataSource);
- // in BeanFactoryDataSourceLookup.getDataSource(String dataSourceName)
- return dataSourceLookup.getDataSource((String)dataSource);
- else
- throw new IllegalArgumentException((new StringBuilder()).append("Illegal data source value - only [javax.sql.DataSource] and String supported: ").append(dataSource).toString());
- }
- //获取连接
- public Connection getConnection()
- throws SQLException
- {
- //再看determineTargetDataSource
- return determineTargetDataSource().getConnection();
- }
- protected DataSource determineTargetDataSource()
- {
- //获取当前数据源名,这里是关键
- Object lookupKey = determineCurrentLookupKey();
- //获取当前数据源
- DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey);
- if(dataSource == null && (lenientFallback || lookupKey == null))
- //如果dataSource为空,则dataSource为默认的数据源resolvedDataSources
- 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;
- }
- //determineCurrentLookupKey方法,为抽象方法,待子类扩展,这是不是给了我们一种思路
- protected abstract Object determineCurrentLookupKey();
- private Map targetDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource
- private Object defaultTargetDataSource;
- private boolean lenientFallback;
- private DataSourceLookup dataSourceLookup;
- private Map resolvedDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource
- private DataSource resolvedDefaultDataSource;
- }
从分析AbstractRoutingDataSource获取数据源得出,想要实现多数据源,只需要扩展AbstractRoutingDataSource,并实现determineCurrentLookupKey方法即可,并在determineCurrentLookupKey方法中切换数据源名即可。
下面实验: 下载
//数据源路由
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- /**
- * 动态切换数据源
- * @author donald
- *
- */
- public class MultipleRoutingDataSource extends AbstractRoutingDataSource{
- @Override
- protected Object determineCurrentLookupKey() {
- return DataSourceContextHolder.getDataSourceType();
- }
}
//这里我们创建一个数据源上下文句柄,以便切换数据源 下载
- /**
- * 数据源上下文
- * @author donald
- *
- */
- public class DataSourceContextHolder {
- public final static String DATA_SOURCE_LOCAL = "dataSource";
- public final static String DATA_SOURCE_SYNC = "syncDataSource";
- //对数据源名,线程隔离
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
- public static void setDataSourceType(String dataSource) {
- contextHolder.set(dataSource);
- }
- public static String getDataSourceType() {
- return contextHolder.get();
- }
- public static void clearDataSourceType() {
- contextHolder.remove();
- }
- }
//配置如下 下载
- <beans>
- <!-- 数据源1 -->
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
- <!-- 数据库基本信息配置 -->
- <property name="url" value="${url}" />
- <property name="username" value="${username}" />
- <property name="password" value="${password}" />
- <property name="driverClassName" value="${driverClassName}" />
- <property name="filters" value="${filters}" />
- <!-- 最大并发连接数 -->
- <property name="maxActive" value="${maxActive}" />
- <!-- 初始化连接数量 -->
- <property name="initialSize" value="${initialSize}" />
- <!-- 配置获取连接等待超时的时间 -->
- <property name="maxWait" value="${maxWait}" />
- <!-- 最小空闲连接数 -->
- <property name="minIdle" value="${minIdle}" />
- </bean>
- <!-- 数据源2 -->
- <bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
- <property name="url" value="${sync.url}" />
- <property name="username" value="${sync.username}" />
- <property name="password" value="${sync.password}" />
- <property name="driverClassName" value="${sync.driverClassName}" />
- <property name="filters" value="${filters}" />
- <property name="maxActive" value="${maxActive}" />
- <property name="initialSize" value="${initialSize}" />>
- <property name="maxWait" value="${maxWait}" />
- <property name="minIdle" value="${minIdle}" />
- </bean>
- <!-- 数据源路由 -->
- <bean id="multipleDataSource" class="com.dataSource.MultipleRoutingDataSource">
- <!-- 默认数据源 -->
- <property name="defaultTargetDataSource" ref="dataSource"/>
- <!-- 目标数据源 -->
- <property name="targetDataSources">
- <map>
- <!-- 注意这里的value是和上面的DataSource的id对应,key要和
- 下面的DataSourceContextHolder中的常量对应 -->
- <entry value-ref="dataSource" key="dataSource"/>
- <entry value-ref="syncDataSource" key="syncDataSource"/>
- </map>
- </property>
- </bean>
- <!-- 配置mybatis -->
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <!-- 这里为multipleDataSource,可以统一管理事务 -->
- <property name="dataSource" ref="multipleDataSource" />
- <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
- <!-- mapper扫描 -->
- <property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
- </bean>
- <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
- <constructor-arg ref="sqlSessionFactory" />
- </bean>
- <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="multipleDataSource"></property>
- </bean>
- <!-- <aop:aspectj-autoproxy proxy-target-class="true" /> -->
- </beans>
手动测试 下载
- @Controller
- @RequestMapping(value="/test")
- public class TestController extends BaseController{
- private static Logger log = LoggerFactory.getLogger(TestController.class);
- @Resource(name = "daoSupport")
- private DaoSupport dao;
- @SuppressWarnings("unchecked")
- @RequestMapping("/db")
- public void testDbSource(HttpServletResponse response) throws IOException {
- log.debug("=======Into testDbSource==============");
- PageData pd = this.getPageData();
- try {
- DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);
- List<PageData> lpd = (List<PageData>) dao.findForList("test.list", pd);
- log.info("=============localDao size:"+lpd.size()+","+DataSourceContextHolder.getDataSourceType());
- DataSourceContextHolder.clearDataSourceType();
- } catch (Exception e) {
- log.error(e.getMessage());
- e.printStackTrace();
- }
- try {
- DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_SYNC);
- List<PageData> lpdTest = (List<PageData>) dao.findForList("test.list", pd);
- log.info("=============syncDao size:"+lpdTest.size()+","+DataSourceContextHolder.getDataSourceType());
- DataSourceContextHolder.clearDataSourceType();
- } catch (Exception e) {
- log.error(e.getMessage());
- e.printStackTrace();
- }
- response.getWriter().write("test");
- }
- }
访问http://localhost:8080/r/test/db.do控制台输出
2016-09-21 17:57:13 -40921 [com.controller.test.TestController] INFO - =============localDao size:5,dataSource
2016-09-21 17:57:13 -40941 [com.controller.test.TestController] INFO - =============syncDao size:4,syncDataSource
上面的方式是手动切换数据源,下面我们通过Spring AOP实现动态切换数据源:
定义注解 下载
- @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface DbSource {
- String value() default "";
- }
定义AOP
- /**
- * 根据DAO的DbSource的值,动态切换数据源
- * @author donald
- *
- */
- @Aspect
- @Component
- public class DataSourceAspect {
- private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
- //定义切点
- @Pointcut("@annotation(com.fh.dataSource.DbSource)")
- public void dbSourceAspect() {}
- /* @Before("dbSourceAspect()")
- public void doBefore(JoinPoint joinPoint) {
- try {
- System.out.println("Dao-class:" + (joinPoint.getTarget().getClass().getName()));
- System.out.println("DataSource:" + getDbSourceValue(joinPoint));
- } catch (Exception e) {
- // 记录本地异常日志
- logger.error("exception", e.getMessage());
- }
- }*/
- /**
- * 切换数据源
- * @param joinPoint
- */
- @Around("dbSourceAspect()")
- public void doAround(ProceedingJoinPoint joinPoint) {
- try {
- log.info("=============Dao-class:" + (joinPoint.getTarget().getClass().getName()));
- log.info("=============DataSource:" + getDbSourceValue(joinPoint));
- joinPoint.proceed();
- DataSourceContextHolder.clearDataSourceType();
- }
- catch(Throwable e){
- log.error("=============Throwable:", e.getMessage());
- }
- }
- /**
- * 获取数据源id
- * @param joinPoint
- * @return
- * @throws Exception
- */
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static String getDbSourceValue(JoinPoint joinPoint) throws Exception {
- //根据连接点获取class
- String targetName = joinPoint.getTarget().getClass().getName();
- Class targetClass = Class.forName(targetName);
- // DbSource dbSource = (DbSource) targetClass.getAnnotation(DbSource.class);
- //根据连接点获取method
- String methodName = joinPoint.getSignature().getName();
- //根据连接点获取args
- Object[] arguments = joinPoint.getArgs();
- Method[] methods = targetClass.getMethods();
- String dbId ="";
- //获取注解连接点的值
- for (Method method : methods) {
- if (method.getName().equals(methodName)) {
- Class[] clazzs = method.getParameterTypes();
- if (clazzs.length == arguments.length) {
- dbId = method.getAnnotation(DbSource.class).value();
- if(!StringUtils.isBlank(dbId)){
- DataSourceContextHolder.setDataSourceType(dbId);
- }
- else{
- dbId = DataSourceContextHolder.DATA_SOURCE_LOCAL;
- DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);
- }
- break;
- }
- }
- }
- return dbId;
- }
- }