需求场景:
数据迁移的过程中,往往数据源不止一个,这些数据源分别分布在不同的主机,不同的域名下,现有一基本库,保存所有主机的信息,包括 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