您的位置:首页 > 移动开发

实现spring多数据源以支持saas或手机App的多公司模式

2015-03-30 20:37 387 查看
当我们开发saas或手机应用程序,涉及多公司时,要求每个公司使用一个数据库,如何配置spring数据源使得每个公司使用不同的数据库连接?

1、将公司id存放到一个ThreadLocal变量中,每次请求时设置,使得每次访问数据源可以从ThreadLocal获取当前请求所属的公司id。

/**
* 用来存放当前线程的数据
*/
public class ThreadHolder {
//公司id
private static ThreadLocal<String> localDid = new ThreadLocal<String>();

public static void putSp(String sp) {
localDid.set(sp);
}

public static String getSp() {
if (localDid.get() == null)
return "";
else
return (String)localDid.get();
}
}
2、在每次请求时调用ThreadHolder.putSp方法设置公司id。

可以定义一个filter,在doFilter方法中调用即可达到每次请求设置的效果。至于客户端请求的公司id参数放在哪里,这个可以灵活设置,可以放在每个请求的request参数中,也可以放在cookie中

3、修改spring数据源配置

原配置(一般使用c0p3数据原的配置)

<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="xxxx" />
<property name="jdbcUrl" value="xxx" />
<property name="user" value="user" />
<property name="password" value="password" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="800" />
<property name="maxIdleTime" value="25000" />
<property name="acquireIncrement" value="1" />
<property name="maxStatements" value="0" />
<property name="initialPoolSize" value="100" />
<property name="idleConnectionTestPeriod" value="18000" />
<property name="acquireRetryAttempts" value="10" />
<property name="acquireRetryDelay" value="1000" />
<property name="breakAfterAcquireFailure" value="false" />
<property name="checkoutTimeout" value="10000" />
<property name="testConnectionOnCheckout" value="false" />
</bean>
修改成:

<bean id="dataSource" class="com.sangfor.frame.multiclient.MultiClientDataSource">
<property name="dataSource">
<ref bean="dataSourceDefault" />
</property>
</bean>
<bean id="dataSourceDefault"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="xxxx" />
<property name="jdbcUrl" value="xxx" />
<property name="user" value="user" />
<property name="password" value="password" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="800" />
<property name="maxIdleTime" value="120" />
<property name="acquireIncrement" value="1" />
<property name="maxStatements" value="0" />
<property name="initialPoolSize" value="100" />
<property name="idleConnectionTestPeriod" value="65" />
<property name="acquireRetryAttempts" value="10" />
<property name="acquireRetryDelay" value="1000" />
<property name="breakAfterAcquireFailure" value="false" />
<property name="checkoutTimeout" value="10000" />
</bean>
MultiClientDataSource:

public class MultiClientDataSource implements DataSource {

public static final String DBNAME_PREFIX = "CLIENT_";
private ComboPooledDataSource dataSource = null;
private Map<String,DataSource> dsMap = new HashMap<String,DataSource>();
private static Object lock=new Object();

public DataSource addDataSource(String did){
try{
synchronized (lock) {
if (dsMap==null)
dsMap = new HashMap<String,DataSource>();
DataSource ds = dsMap.get(did);
if (ds != null)
return ds;
ComboPooledDataSource newDs = getNewDataSource(did);
dsMap.put(did, newDs);
}
}catch(Exception e){
e.printStackTrace();
}
return dsMap.get(did);
}
public void removeDataSource(String did){
if (dsMap==null)
return;
dsMap.remove(did);
}
private ComboPooledDataSource getNewDataSource(String did) throws Exception{
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("xxx");//driver class
String jdbcUrl = "jdbc:mysql://127.0.0.1" //一般将ip配置在配置文件
+":3306"+"/"+ DBNAME_PREFIX + did
+ "?useUnicode=true&characterEncoding=utf-8";
ds.setJdbcUrl(jdbcUrl);
ds.setUser("user");
ds.setPassword("password");
ds.setMinPoolSize(1);
ds.setMaxPoolSize(800);
ds.setMaxIdleTime(2000);
ds.setAcquireIncrement(1);
ds.setMaxStatements(0);
ds.setInitialPoolSize(100);
ds.setIdleConnectionTestPeriod(1800);
ds.setAcquireRetryAttempts(10);
ds.setAcquireRetryDelay(1000);
ds.setBreakAfterAcquireFailure(false);
ds.setCheckoutTimeout(10000);
ds.setPreferredTestQuery(" select FORMID from FLOW_FORM where 1 = 2");
ds.setTestConnectionOnCheckout(false);
return ds;
}

public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}

public Connection getConnection(String arg0, String arg1)
throws SQLException {
return getDataSource().getConnection(arg0, arg1);
}

public PrintWriter getLogWriter() throws SQLException {
return getDataSource().getLogWriter();
}

public int getLoginTimeout() throws SQLException {
return getDataSource().getLoginTimeout();
}

public void setLogWriter(PrintWriter arg0) throws SQLException {
getDataSource().setLogWriter(arg0);
}

public void setLoginTimeout(int arg0) throws SQLException {
getDataSource().setLoginTimeout(arg0);
}
public DataSource getDataSource(String dataSourceName)throws SQLException {
try{
if(dataSourceName==null||dataSourceName.equals("")){
return this.dataSource;
}else{
DataSource ds = dsMap.get(dataSourceName);
if (ds!=null)
return ds;
else
return null;
}
}catch(NoSuchBeanDefinitionException ex){
throw new SQLException("There is not the dataSource <name:"+dataSourceName+"> in the applicationContext!");
}
}

public void setDataSource(ComboPooledDataSource dataSource) {
this.dataSource = dataSource;
}

public ComboPooledDataSource getDataSource()throws SQLException{

String did = ThreadHolder.getSp();
DataSource  ds = getDataSource(did);
if (ds == null)
return null;
else
return (ComboPooledDataSource)ds;
}
public DataSource getDefaultDataSource()throws SQLException{

return dataSource;
}
@Override
public boolean isWrapperFor(Class<?> arg0) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public <T> T unwrap(Class<T> arg0) throws SQLException {
// TODO Auto-generated method stub
return null;
}

}
实现原理:

自己新建一个MultiClientDataSource类(实现DataSource接口)去代替原来注入dataSource的ComboPooledDataSource,MultiClientDataSource类的实现精华在于使用一个map,用公司id(did)作为key,value为connection,在getConnection方法中判断是否在map中已经有该did的key,如果没有就新建connection,并且加入到map中
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: