情况:
因项目需要,需要使用动态数据源,什么叫动态数据源,就是数据源是由客户动态选择的,不仅仅有一个。这就意味在后台会配置多个数据源。
我们的系统有很多版本,不同版本开发在不同的数据库上,但是系统需要的一些配置依赖于数据库,所以需要有一个能配置数据源的后台,让开发人员自己处理。
解决方案:
数据库datasource,需要继承AbstractRoutingDataSource ,在spring包里面。
/**
* <p>
* Title: 动态获取数据源
* </p>
*
* <p>
* Description:
* </p>
*
* <p>
* Copyright: 融博技术有限公司 2010
* </p>
*
* @author 袁泉锋HO174959
* @version 1.0
* @date Jul 23, 2010
*
*/
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
/**
* <Description>寻找当前线程数据库Key值
*
* @since Jul 23, 2010
* @return <Description>
*
*/
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getDataBase();
}
}
AbstractRoutingDataSource 做了简单处理,我是反编译的,然后将属性targetDataSources修改为了map结构,key为数据源的ID,value是正常的datasource,看下面的spring配置文件:
package com.rb.util;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.util.Assert;
public abstract class AbstractRoutingDataSource extends AbstractDataSource
implements InitializingBean {
public AbstractRoutingDataSource() {
dataSourceLookup = new JndiDataSourceLookup();
}
public void setTargetDataSources(Map targetDataSources) {
this.targetDataSources = targetDataSources;
}
public Map getTargetDataSources() {
return this.targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = ((DataSourceLookup) (dataSourceLookup == null ? ((DataSourceLookup) (new JndiDataSourceLookup()))
: dataSourceLookup));
}
public void afterPropertiesSet() {
if (targetDataSources == null)
throw new IllegalArgumentException("targetDataSources is required");
resolvedDataSources = new HashMap(targetDataSources.size());
Object lookupKey;
DataSource dataSource;
for (Iterator it = targetDataSources.entrySet().iterator(); it
.hasNext(); resolvedDataSources.put(lookupKey, dataSource)) {
java.util.Map.Entry entry = (java.util.Map.Entry) it.next();
lookupKey = resolveSpecifiedLookupKey(entry.getKey());
dataSource = resolveSpecifiedDataSource(entry.getValue());
}
if (defaultTargetDataSource != null)
resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource);
}
protected DataSource resolveSpecifiedDataSource(Object dataSource)
throws IllegalArgumentException {
if (dataSource instanceof DataSource)
return (DataSource) dataSource;
if (dataSource instanceof String)
return dataSourceLookup.getDataSource((String) dataSource);
else
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: "
+ dataSource);
}
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password)
throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert
.notNull(resolvedDataSources,
"DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource) resolvedDataSources.get(lookupKey);
if (dataSource == null)
dataSource = resolvedDefaultDataSource;
if (dataSource == null)
throw new IllegalStateException(
"Cannot determine target DataSource for lookup key ["
+ lookupKey + "]");
else
return dataSource;
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
protected abstract Object determineCurrentLookupKey();
private Map targetDataSources;
private Object defaultTargetDataSource;
private DataSourceLookup dataSourceLookup;
private Map resolvedDataSources;
private DataSource resolvedDefaultDataSource;
}
/*
DECOMPILATION REPORT
Decompiled from: E:\myeclipse6.0_project\ToolServerFlex\WebRoot\WEB-INF\lib\spring.jar
Total time: 218 ms
Jad reported messages/errors:
The class file version is 48.0 (only 45.3, 46.0 and 47.0 are supported)
Exit status: 0
Caught exceptions:
*/
CustomerContextHolder如下:
/**
* <p>
* Title: 动态获取数据源
* </p>
*
* <p>
* Description:
* </p>
*
* <p>
* Copyright: 融博技术有限公司 2010
* </p>
*
* @author 袁泉锋HO174959
* @version 1.0
* @date Jul 23, 2010
*
*/
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataBase(String dataBase) {
contextHolder.set(dataBase);
}
public static String getDataBase() {
return (String) contextHolder.get();
}
public static void clearDataBase() {
contextHolder.remove();
}
}
开发人员请求过来的时候,调用方法setDataBase,将数据库配置key放入线程变量中。
自己定义一个数据库事务管理器:
package com.rb.util;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
public class TransactionManager {
//private Connection conn;
private DataSource dataSource;
public static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public TransactionManager() {
}
// public synchronized Connection getConnection(){
// Connection conn = tl.get();
// if(conn==null){
// try {
// conn = dataSource.getConnection();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// tl.set(conn);
// }
// return conn;
// }
/** 开启事务 */
public void beginTransaction() throws SQLException{
try {
Connection conn = tl.get();
if(conn==null){
conn = dataSource.getConnection();
tl.set(conn);
}
conn.setAutoCommit(false); //把事务提交方式改为手工提交
} catch (SQLException e) {
throw new SQLException("开户事务时出现异常");
}
}
/** 提交事务并关闭连接 */
public void commitAndClose() throws SQLException{
Connection conn = null;
try {
conn = tl.get();
conn.commit(); //提交事务
} catch (SQLException e) {
throw new SQLException("提交事务时出现异常");
}finally{
if(conn!=null){
conn.close();
}
tl.remove(); //卸装线程绑定
}
}
/** 回滚并关闭连接 */
public void rollbackAndClose()throws SQLException{
Connection conn = null;
try {
conn = tl.get();
conn.rollback();
} catch (SQLException e) {
throw new SQLException("回滚事务时出现异常");
} finally{
if(conn!=null){
conn.close();
}
tl.remove(); //卸装线程绑定
}
}
}
调用完setDataBase方法之后,调用beginTransaction方法,第一次取的时候,connection是null,然后去datasource取连接,这个时候的datasource里面有很多数据源的连接,因为是一个map结构。当调用conn = dataSource.getConnection();的时候,会调用AbstractRoutingDataSource类的
方法:
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
然后是:
protected DataSource determineTargetDataSource() {
Assert
.notNull(resolvedDataSources,
"DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource) resolvedDataSources.get(lookupKey);
if (dataSource == null)
dataSource = resolvedDefaultDataSource;
if (dataSource == null)
throw new IllegalStateException(
"Cannot determine target DataSource for lookup key ["
+ lookupKey + "]");
else
return dataSource;
}
里面会用到方法:
Object lookupKey = determineCurrentLookupKey();
但是我们的子类重写了他,是去线程变量里面取刚才我们setDataBase进去的,所以只会取到一个datasource。
这样就能取到自己选择的数据库连接了。