spring里面事务的传播属性和事务隔离级别 : http://blog.csdn.net/it_man/article/details/5074371
第一种方法:创建两个会话工厂
配置如下
>
这种方法,在程序中只需要切换sqlSessionTemplate即可,但是这样第二数据源无事务,
容易抛出TransactionSynchronizationManager.unbindResourceIfPossible异常,这也是其缺点
是否可以再添加一个事务管理器?
你可以试试,我测试没问题。
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,key为数据源名,value为DataSource private Object defaultTargetDataSource; private boolean lenientFallback; private DataSourceLookup dataSourceLookup; private Map resolvedDataSources;//Map ,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 ThreadLocalcontextHolder = new ThreadLocal (); public static void setDataSourceType(String dataSource) { contextHolder.set(dataSource); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } }
//配置如下
>
手动测试
@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); Listlpd = (List ) 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 lpdTest = (List ) 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 */ /*有参数的处理 Object[] args = joinPoint.getArgs(); if(args != null && args.length > 1) { log.info("==============Around-args:"+JsonUtil.toJson(args)); obj = joinPoint.proceed(args); } else{ obj = joinPoint.proceed(); }*/ @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()); } } /** * 切换数据源,当注解方法有返回值的处理情况 * @param joinPoint */ /* @Around("dbSourceAspect()") public Object doAround(ProceedingJoinPoint joinPoint) { //切入方法返回对象 Object obj = null; try { log.debug("=============class:" + (joinPoint.getTarget().getClass().getName())); log.debug("=============DataSource:" + getDbSourceValue(joinPoint)); obj = joinPoint.proceed(); } catch(Throwable e){ log.error("=============Throwable:", e.getMessage()); e.printStackTrace(); } finally{ DataSourceContextHolder.clearDataSourceType(); } return obj; } */ /** * 获取数据源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; } }
测试
@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("/db1") @DbSource(DataSourceContextHolder.DATA_SOURCE_LOCAL) public void testDbSource1(HttpServletResponse response) throws IOException { log.info("=======Into testDbSource1=============="); PageData pd = this.getPageData(); try { Listlpd = (List ) dao.findForList("test.list", pd); log.info("=============localDao size:"+lpd.size()+","+DataSourceContextHolder.getDataSourceType()); } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } response.getWriter().write("db1"); } @SuppressWarnings("unchecked") @RequestMapping("/db2") @DbSource(DataSourceContextHolder.DATA_SOURCE_SYNC) public void testDbSource2(HttpServletResponse response) throws IOException { log.info("=======Into testDbSource2=============="); PageData pd = this.getPageData(); try { List lpdTest = (List ) dao.findForList("test.list", pd); log.info("=============synDao size:"+lpdTest.size()+","+DataSourceContextHolder.getDataSourceType()); } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } response.getWriter().write("db2"); } }
访问http://localhost:8080/r/test/db1.do,控制台输出
2016-09-21 17:58:12 -99217 [com.dataSource.DataSourceAspect] INFO - =============Dao-class:com.controller.test.TestController
2016-09-21 17:58:12 -99220 [com.dataSource.DataSourceAspect] INFO - =============DataSource:dataSource
2016-09-21 17:58:12 -99220 [com.controller.test.TestController] INFO - =======Into testDbSource1==============
2016-09-21 17:58:12 -99225 [com.controller.test.TestController] INFO - =============localDao size:5,dataSource
访问http://localhost:8080/r/test/db2.do,控制台输出
2016-09-21 17:58:16 -104167 [com.dataSource.DataSourceAspect] INFO - =============Dao-class:com.controller.test.TestController
2016-09-21 17:58:16 -104167 [com.dataSource.DataSourceAspect] INFO - =============DataSource:syncDataSource
2016-09-21 17:58:16 -104168 [com.controller.test.TestController] INFO - =======Into testDbSource2==============
2016-09-21 17:58:17 -104180 [com.controller.test.TestController] INFO - =============synDao size:4,syncDataSource
至此动态切换成功,
从上面可以看出,SqlSessionFactoryBean的获取数据源是通过数据源路由,
我们通过扩展数据源路由来实现,动态切换数据源。
注意:
当动态数据源切换失败时,查看是不是事务因素,要先理解事务,例如你的事务是放在service...,你切换数据库时为了保证事务的完整性,你应该在进入service之前切换掉数据源,因为如果你是在service方法中切换数据源那是不可行的,因为这个时候spring已经打开了一个事务,他会阻止你切换,所以你应在这之前切换,然后进入service方法,.这样spring又给你新切换的数据源加上事务了