论坛首页 Java版 Spring

动态切换多数据源

浏览 17456 次
该帖已经被评为良好帖
作者 正文
时间:2006-07-03
正在作的项目有以下需求:
在用户登陆介面有选择框,可以让用户选择使用哪个数据库,每个用户的选择也许不同,也就是说当前服务器上同时存在多个数据源的引用.
数据库的个数是可变的,但在系统运行后就固定了,如果需要增加,必须停止服务进行添加.

以往在另一个使用hibernate项目中,也实现了上面的功能,当时是注册多个sessionFactroy到jboss的不同的jndi,然后根据用户的选择,就可以选择使用哪个数据库.而且由于所有的事务都由自己控制,所以基本上是可行的.

现在采用spring+hibernate,事务由spring管理,不知是否可行.另外不知如何根据用户选择来切换到他要用的数据库上,而不影响别人.
以下是部分设置

application-context.xml
[code:1]
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- <property name="mappingResources">
<value>petclinic.hbm.xml</value>
</property>-->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.generate_statistics">true</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean
class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" />
</entry>
</map>
</property>
</bean>

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="dataSource1"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<bean id="sessionFactory1"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource1" ref="dataSource" />
<!-- <property name="mappingResources">
<value>petclinic.hbm.xml</value>
</property>-->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.generate_statistics">true</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean
class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" />
</entry>
</map>
</property>
</bean>

<bean id="transactionManager1"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory1" ref="sessionFactory" />
</bean>

<bean id="asyuserDAO" class="xxx.dao.hibernate.AsymodDAOImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
[/code:1]
AsymodDAOImpl.java
[code:1]
public class AsyuserDAOImpl extends HibernateDaoSupport implements AsyuserDAO {
public Asyuser getUserById(String userNo) {
return (Asyuser) getHibernateTemplate().get(Asyuser.class, userNo);
}
}
[/code:1]
以上DAO实现中的sessionFactroy是由xml中动态注入的,但写死了,我如果想这个DAO从另一个数据库取资料,基本不可能.
能否去掉<property name="sessionFactory" ref="sessionFactory"/>
而在程序中根据用户选择而注入不同sessionFactory.
不过说到这里,又考虑到另一个问题,AsyuserDAOImpl 是单例,非线程安全的,sessionFactory这个属性也许会冲突,如果是这样,那么是不是让dao非单例呢.但这样创建对象的性能消耗是否很大.

就目前来说,我打算先使用这种方法,先不管性能如何,只希望不会有内存泄漏.我现在还不知道如何在程序中得到sessonFactory这个bean ,然后传给DAO.请众位大大指点.
   
时间:2006-07-03
刚刚思考了一下,有了进一步的想法.在DAO层添加BeanFactoryAware,很容易的得到BeanFactory,然后就可以得到指定的sessionFactory.
使用一个BaseDAO.java
[code:1]
public interface BaseDao extends BeanFactoryAware{

}
[/code:1]
AsyuserDAO.java
[code:1]
public interface AsyuserDAO extends BaseDAO{
}
[/code:1]
AsyuserDAOImpl.java
[code:1]
public class AsyuserDAOImpl extends HibernateDaoSupport implements AsyuserDAO {
public Asyuser getUserById(String userNo) {
return (Asyuser) getHibernateTemplate().get(Asyuser.class, userNo);
}

public void setBeanFactory(BeanFactory arg0) throws BeansException {
arg0.getBean("sessionFactory");
this.setSessionFactory((SessionFactory) arg0);
}
}
[/code:1]

这种方法不知是否可行?
线程安全的问题该如何解决呢?
   
0 请登录后投票
时间:2006-07-03
刚刚看到一篇文章,
开发线程安全的Spring Web应用
http://www.javafan.net/article/20050814100024212.html

从这篇文章,我觉得HibernateDaoSupport应该是线程安全的,即使DAO对象是单例.不知道是不是这个意思?

另外,文章中说到,在事务管理器每次执行时,会把Hibernate Session绑定到当前线程,这个时刻应该是在调用DAO对象前,因为事务是针对业务方法设定的.
这样就有些麻烦了,再在DAO中进行新的sessionFactory设定已经失去意义了.不知道是不是这种情况,还需测试一下.
[/url]
   
0 请登录后投票
时间:2006-07-04
做一个FactoryBean, dao让factory在运行中动态返回嘛。
   
0 请登录后投票
时间:2006-07-04
dwangel
不是很清楚你的想法,能否详细说明.谢谢

由于现在采用spring,事务针对业务方法,而不是DAO,所以数据源的动态切换就有些麻烦阿.
   
0 请登录后投票
时间:2006-07-06
dwangel 写道
做一个FactoryBean, dao让factory在运行中动态返回嘛。

可不可以让dataSource在FactoryBean中动态返回呢?还有这个FactoryBean里如何注入用户选择的参数呢.是不是这个意思,谢谢:
[code:1]
<bean id="userDao" class="test.Dao.UserDaoFactoryBean">
<property name="daoImpls">
<list>
<bean>userDaoImpl1</bean>
<bean>userDaoImpl2</bean>
</list>
</property>
</bean>
[/code:1]
   
0 请登录后投票
时间:2006-07-06
关于Sessionfactory的动态切换,我已经实现了,在设置文件中设置好几个sessionFactory,其id是与用户选择的数据库标示相同.然后在dao中根据不同数据库从context中取回不同的sessionFactory.
我重新实现了HibernateDaoSupport这个类,代替spring的那个,用来封装切换.因为如果dao继承spring的这个类,在系统装载context时就必须要求强制注入一个sessionFactory给dao.但实际上,我的系统在装载context时根本不需要知道当前是连接哪个数据库.

具体可以参考http://www.jroller.com/page/thuss?entry=hibernate_mapping_one_class_to


另外,
但是,有关声明事务的问题,还是无法解决.在xml中,我是声明了bo的事务代理,但只能固定到某个sessionFactory.在DAO那里可以动态切换sessionFactory,但是在事务管理器这里,怎么动态切换呢,如何让bo的方法可以动态切换不同的事务.
我看到这里一篇文章,TransactionProxyFactoryBean的另类用法http://blog.matrix.org.cn/page/litaojian
是不是可以在bo被调用前就在程序中动态声明事务.
   
0 请登录后投票
时间:2006-07-07
哦,想成DAO接口不发生任何变化去了,这样Singleton是无法实现的.
   
0 请登录后投票
时间:2006-07-11
通过改造sessionFactory不就得了!或者需要动态得程序性控制,那就像楼上所说的,得到个beanFactory自己去组装!
   
0 请登录后投票
时间:2006-07-21
最近遇到多数据源的问题,找了好多资料一直没有找到很好的解决方法。
以下是自己利用ThreadLocal实现多数据源的方案.
利用DBCP的数据源来实现自己的数据源。
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
public class CommonDataSource implements DataSource {
private Map dataSourceMap = null;
public int getLoginTimeout() throws SQLException {
return createDataSource().getLoginTimeout();
}
public void setLoginTimeout(int seconds) throws SQLException {
createDataSource().setLoginTimeout(seconds);
}
public PrintWriter getLogWriter() throws SQLException {
return createDataSource().getLogWriter();
}
public void setLogWriter(PrintWriter out) throws SQLException {
createDataSource().setLogWriter(out);
}
public Connection getConnection() throws SQLException {
return createDataSource().getConnection();
}
public Connection getConnection(String username, String password)
throws SQLException {
return createDataSource().getConnection(username, password);
}
/**
* @return Returns the dataSourceMap.
*/
public Map getDataSourceMap() {
return dataSourceMap;
}
/**
* @param dataSourceMap
* The dataSourceMap to set.
*/
public void setDataSourceMap(Map dataSourceMap) {
this.dataSourceMap = dataSourceMap;
}
protected BasicDataSource createDataSource() throws SQLException {
String sp = (String) SpObserver.getSp();
if (sp == null) {
throw new SQLException(
"Cannot create datasource because of missing sp");
}
String dataSourceName = "dataSource" + sp;
BasicDataSource dataSource = (BasicDataSource) dataSourceMap
.get(dataSourceName);
return dataSource;
}

以下是SpObserver.java
public class SpObserver {
private static ThreadLocal local=new ThreadLocal();

public static void putSp(Object sp){
local.set(sp);
}

public static Object getSp(){
return local.get();
}
}

前面用一个Filter来设置指定的数据源。

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String dataSource = httpRequest.getParameter("dataSource");
SpObserver.putSp(dataSource);
chain.doFilter(request, response);
}

有需要源码的可以与我联系wjw_319@hotmail.com
   
0 请登录后投票
论坛首页 Java版 Spring

跳转论坛: