实现 spring + mybatis 动态切换 datasource

需求场景:

数据迁移的过程中,往往数据源不止一个,这些数据源分别分布在不同的主机,不同的域名下,现有一基本库,保存所有主机的信息,包括 host, port , database 等信息。目的是取得每个host 的连接并迁移数据。


问题:

首先spring 集成 mybatis 并获取数据连接之前,要实例化datasource 对象,但是datasource 保存在数据库中,要从数据库中获取host 等信息才能实现实例化,这就好像是一个鸡生蛋,蛋孵鸡的问题。

解决方案:

在spring 完成实例化之前,AbstractRoutingDataSource 可获取数据库连接,拿到 host 的信息,在实现每个datasource 的实例化。


分析代码:

Public class MultiDataSource extends AbstractRoutingDataSource{


	private static List<ArchiveHost> hostList ;

	@Override
	protected Object determineCurrentLookupKey(){
		return DataSourceHolder.getDataSourceName();
	}


	public List<ArchiveHost> initHostDataSource(){
		Connection conn = null;
		ResultSet rs = null;
		PreparedStatement ps = null;
		hostList = new ArrayList<ArchiveHost>();
		try {
			conn = this.getConnection();
			ps = conn
					.prepareStatement("SELECT host_id,host_name,port,db_name FROM archive_host");
			 rs = ps.executeQuery();
			 while(rs.next()){
				 ArchiveHost archiveHost = new ArchiveHost();
				 archiveHost.setHost_id(rs.getInt("host_id"));
				 archiveHost.setHost_name(rs.getString("host_name"));
				 archiveHost.setPort(rs.getString("port"));
				 archiveHost.setDb_name(rs.getString("db_name"));
				 hostList.add(archiveHost);
			 }

		} catch (SQLException e) {
			throw new RuntimeException(e);
		}finally {
			try {
				ps.close();
				rs.close();
				conn.close();
			} catch (SQLException e) {
				throw new RuntimeException(e);
			}
		}
		return hostList;
	}

定义 MultiDataSource 继承 AbstractRoutingDataSource,可以获取 Connection ,在调用上面的 initHostDataSource 之前,首先要设置基本库的信息,要不怎么实例化 Connection,代码如下:

	@Bean
	public MultiDataSource dataSource() throws SQLException{
		MultiDataSource dataSource = new MultiDataSource();
		dataSource.setLenientFallback(false);
		dataSource.setDefaultTargetDataSource(dataSourceWriteMySQL());
		
		Map<Object,Object> targetDataSources = new HashMap<>();
		targetDataSources.put(Type.MYSQL.getValue(), dataSourceMySQL());
		dataSource.setTargetDataSources(targetDataSources);
		dataSource.afterPropertiesSet(); 
		List<ArchiveHost> hostList =  dataSource.initHostDataSource();
		for (ArchiveHost archiveHost : hostList){
			String url = "jdbc:mysql://"+archiveHost.getHost_name()+":"+archiveHost.getPort()+"/"+archiveHost.getDb_name()+"?useUnicode=true&characterEncoding=utf-8";
			targetDataSources.put(archiveHost.getDb_name(),initDataSource( "com.mysql.jdbc.Driver",  url,  "xxx",  "xxx"));
		}
		dataSource.setTargetDataSources(targetDataSources); 
		dataSource.afterPropertiesSet();

		return dataSource;
	}
完成上面的过程之后,spring 就会在初始化容器之前,将所有的host datasource 都实例化,并保存在容器中。那么问题来了,怎么使用呢?:在实例化SqlSessionFactoryBean ,有这个过程:
	@Bean
	public DruidDataSource dataSourceMySQL() throws SQLException{
		return initDataSource(driverMysql,urlSlaveMysql,userSlaveMysql,passwordSlaveMysql);
	}
	private DruidDataSource initDataSource(String driver,String url,String user,String password) throws SQLException{
		DruidDataSource source = new DruidDataSource();
		source.setDriverClassName(driver);
		source.setUrl(url);
		source.setUsername(user);
		source.setPassword(password);
		
		/**datasource 的常用配置,省略。。**/
		return source;
	}



	@Bean
	public SqlSessionFactoryBean sqlSessionFactory(MultiDataSource dataSource) throws IOException{
		ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
		factory.setDataSource(dataSource);
		factory.setMapperLocations(resolver.getResources(mapperLocation));
		
		return factory;
	}

同时你需要一个本地线程来管理你的datasource

// 线程本地环境
	private static final ThreadLocal<DataSourceQuene> contextHolderQueue = new ThreadLocal<DataSourceQuene>(); 
	
	// 设置数据源类型
	public static void setDataSourceName(String dataSourceName){
		DataSourceQuene queue = contextHolderQueue.get();
		if(queue==null){
			queue = new DataSourceQuene();
			contextHolderQueue.set(queue);
		}
		queue.putDataSource(dataSourceName);
	}

	// 获取数据源类型
	public static String getDataSourceName(){
		DataSourceQuene queue = contextHolderQueue.get();
		return queue==null?null:queue.getDataSource();
	}

	// 清除数据源类型
	public static void clearDataSourceName(){
		DataSourceQuene queue = contextHolderQueue.get();
		queue.removeDataSource();
		if(queue.getDataSourceSize()==0){
			contextHolderQueue.remove();
		}
	}

现在你可以启动spring 容器,测试下面的代码:

  
  
  

	@Test
	public void testDataSource(){
		List<ArchiveHost> hostList = MultiDataSource.getHostList();
		for (ArchiveHost host : hostList){
			DataSourceHolder.setDataSourceName(host.getHost_name()); //设置数据源
//			 mysqlMapper.XXXXX(); //执行SQL 
			DataSourceHolder.clearDataSourceName(); // 清理数据源

		}
	}
mysqlMapper.XXXXX();会引用不同的数据源执行sql



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值