1、 根据用户的选择,使用不同的数据源。
2、 解决思路锁定:将sessionFactory的属性dataSource设置成不同的数据源,以达到切换数据源的目的。
3、 问题产生:因为整个项目用的几乎都是单例模式,当多个用户并发访问数据库的时候,会产生资源争夺的问题。即项目启动时候,所有的bean都被装载到内存,并且每个bean都只有一个对象。正因为只有一个对象,所有的对象属性就如同静态变量(静态变量跟单例很相似,常用静态来实现单例)。整个项目系统的dataSource只有一个,如果很多用户不断的去改变dataSource的值,那必然会出现资源的掠夺问题,造成系统隐患。
4、 多资源共享解决思路:同一资源被抢夺的时候,通常有两种做法,a、以时间换空间 b、以空间换时间。
5、 线程同步机制就是典型的“以时间换空间”,采用排队稍等的方法,一个个等待,直到前面一个用完,后面的才跟上,多人共用一个变量,用synchronized锁定排队。
6、 “ThreadLocal”就是典型的“以空间换时间”,她可以为每一个人提供一份变量,因此可以同时访问并互不干扰。
7、 言归正传:sessionFactory的属性dataSource设置成不用的数据源,首先不能在配置文件中写死,我们必须为她单独写一个类,让她来引用这个类,在这个类中再来判断我们到底要选择哪个数据源。
8、 我们把这类取个名字:MultiDataSource.,java,在这个类中要有DataSource的属性,还有方便我们取得配置文件中的数据源,我们还需要ApplicationContext这个属性,有这两个属性就够了。下面是我自己写的MultiDataSource类的源代码:
public class MultiDataSource implements DataSource {
private DataSource dateSource=null;
public void setDateSource(DataSource dateSource) {
this.dateSource = dateSource;
}
public DataSource getDateSource() {
return this. dateSource;
}
//其他实现方法自己写,主要是上面几个;
}
9、 在单例模式下,我们的系统中只有一个DataSource对象,我们每次调用MultiDataSource都可能是不同的,所以我们把DataSource放在实例变量中,最直接的方法是在getDataSource中告诉她,增加参数让她知道我们需要哪个数据源。
public DataSource getDateSource(String dataSourceName) {
try{
if(dataSourceName==null || dataSourceName.equals("")){
return this.dateSource;
}
return (DataSource)this.applicationContext.getBean(dataSourceName); }catch(NoSuchBeanDefinitionException ex){
return this.dateSource;
}
10、 }
11、 注意的是:DataSourceName就是配置文件中对应的数据源Id
ApplicationContext.xml中数据源的代码:
<bean id="DateSource1"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"
value="oracle.jdbc.driver.OracleDriver">
</property>
<property name="jdbcUrl"
value="jdbc:oracle:thin:@192.168.1.113:1521:orcl">
</property>
<property name="user" value="hycs"></property>
<property name="password" value="hycs"></property>
<property name="maxPoolSize" value="40"></property>
<property name="minPoolSize" value="1"></property>
<property name="initialPoolSize" value="1"></property>
<property name="maxIdleTime" value="20"></property>
</bean>
<bean id="DateSource2"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"
value="oracle.jdbc.driver.OracleDriver">
</property>
<property name="jdbcUrl"
value="jdbc:oracle:thin:@192.168.1.12:1521:orcl">
</property>
<property name="user" value="hycs"></property>
<property name="password" value="hycs"></property>
<property name="maxPoolSize" value="40"></property>
<property name="minPoolSize" value="1"></property>
<property name="initialPoolSize" value="1"></property>
<property name="maxIdleTime" value="20"></property>
12、 </bean>
13、 再说一次sessionfactory在*.xml中的配置
<bean id="multiDataSource" class="com.hy.datasource.MultiDataSource">
<property name="dateSource" ref="DateSource1"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="multiDataSource" />
</property>
。
。
。其他自己补上
。
。
</bean>
14、 sessionfactory的属性DataSource已经不是直接ref 配置文件中的数据源了。
15、 她ref的是我们在自己编写的类,在一个封装类里面单独判断,这在外部是完全感觉不到我们数据源的改变。
16、 为了在类中得到ApplicationContext,我们必须让MultiDataSource实现org.springframework.context.ApplicationContextAware接口,并实现其方法:
private ApplicationContext applicationContext=null;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
17、 这样就可以通过this.applicationContext.getBean(dataSourceName)得到dataSource;
18、 写到这里差不多已经完毕了,但是页面上的数据源名字怎么通过request传到MultiDataSource类里呢,难道我们还要通过每个业务层和数据层一层层的传递数据源的名字吗?这样写也太多了吧。
在这里我们可以通过线程的方式跳过业务层和数据层直接把值传递到MultiDataSource类里public class SpObserver {
@SuppressWarnings("unchecked")
private static ThreadLocal<String> local = new ThreadLocal();
@SuppressWarnings("unchecked")
public static void putSp(String sp) {
local.set(sp);
}
public static String getSp() {
return local.get();
}
19、 }
20、 做一个filter,每一次请求的时候就调用SpObserver.putSp(dataSources);
把request中的dataSourceName传给SpObserver对象并改变MultiDataSource类中getDataSource方法。public DataSource getDateSource() {
String sp=SpObserver.getSp();
return this.getDateSource(sp);
21、 }
22、 小小说明:每一次请求如果都发送数据源的名字未免也太麻烦了,可以在发送请求的时候把她保存在session中,如果下次没发请求的话,自己去session中取值,如果再发请求的时候就改变session中保存的值。
23、 总的来说就增加MultiDataSource和SpObserver两个类,其他的就是filter和配置文件的事了。下面我把详细的代码附上去;
MultiDataSource类的完整代码:
package com.hy.datasource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class MultiDataSource implements DataSource,ApplicationContextAware{
private DataSource dateSource=null;
private ApplicationContext applicationContext=null;
public Connection getConnection() throws SQLException {
// TODO Auto-generated method stub
return getDateSource().getConnection();
}
public Connection getConnection(String arg0, String arg1)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
public void setLogWriter(PrintWriter arg0) throws SQLException {
// TODO Auto-generated method stub
}
public void setLoginTimeout(int arg0) throws SQLException {
// TODO Auto-generated method stub
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public DataSource getDateSource(String dataSourceName) {
System.out.println("线程进来2");
try{
if(dataSourceName==null || dataSourceName.equals("")){
System.out.println("线程进来3");
return this.dateSource;
}
System.out.println("线程进来4");
return (DataSource)this.applicationContext.getBean(dataSourceName);//根据dataSourceName加载配置文件中的数据源对象
}catch(NoSuchBeanDefinitionException ex){
System.out.println("线程进来5");
return this.dateSource;
}
}
public void setDateSource(DataSource dateSource) {
System.out.println("dataSource方法");
this.dateSource = dateSource;
}
/**
* 项目启动时,默认使用defaultDataSource
* 用户选择时,根据选择数据源
* ThreadLocal在单例下生成多个线程变量副本,解决多用户并发访问
*/
public DataSource getDateSource() {
System.out.println("线程进来1");
String sp=SpObserver.getSp();
return this.getDateSource(sp);
}
}
24、 SpObserver的完整代码:
25、 package com.hy.datasource;
public class SpObserver {
@SuppressWarnings("unchecked")
private static ThreadLocal<String> local = new ThreadLocal();
@SuppressWarnings("unchecked")
public static void putSp(String sp) {
System.out.println("set方法");
local.set(sp);
}
public static String getSp() {
System.out.println("get方法");
return local.get();
}
}
24、MyFilter的完整代码:
public class MyFilter implements Filter {
public void destroy() {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest) request;
String dataSources=req.getParameter("dataSource");
String dataSource=(String) req.getSession().getAttribute("dataSource");
if(StringUtils.hasText(dataSources)){
SpObserver.putSp(dataSources);
}else if(StringUtils.hasText(dataSource)){
SpObserver.putSp(dataSource);
}
chain.doFilter(request, response);
}
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
26、 配置文件的部分配置:web.xml的过滤器配置
<filter>
<filter-name>dsFilter</filter-name>
<filter-class>com.hy.datasource.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>dsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
27.ApplicationContext.xml
<bean id="DateSource1"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"
value="oracle.jdbc.driver.OracleDriver">
</property>
<property name="jdbcUrl"
value="jdbc:oracle:thin:@192.168.1.113:1521:orcl">
</property>
<property name="user" value="hycs"></property>
<property name="password" value="hycs"></property>
<property name="maxPoolSize" value="40"></property>
<property name="minPoolSize" value="1"></property>
<property name="initialPoolSize" value="1"></property>
<property name="maxIdleTime" value="20"></property>
</bean>
<bean id="DateSource2"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"
value="oracle.jdbc.driver.OracleDriver">
</property>
<property name="jdbcUrl"
value="jdbc:oracle:thin:@192.168.1.12:1521:orcl">
</property>
<property name="user" value="hycs"></property>
<property name="password" value="hycs"></property>
<property name="maxPoolSize" value="40"></property>
<property name="minPoolSize" value="1"></property>
<property name="initialPoolSize" value="1"></property>
<property name="maxIdleTime" value="20"></property>
</bean>
<bean id="multiDataSource" class="com.hy.datasource.MultiDataSource">
<property name="dateSource" ref="DateSource1"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="multiDataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.Oracle9Dialect
</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/hy/bean/Job.hbm.xml</value>
<value>com/hy/bean/News.hbm.xml</value>
<value>com/hy/bean/ProductInfo.hbm.xml</value>
<value>com/hy/bean/Sort.hbm.xml</value>
<value>com/hy/bean/Userinfo.hbm.xml</value>
<value>com/hy/bean/Message.hbm.xml</value>
</list>
</property>
</bean>
总结:
1、上面的方案是针对不同数据库但表的结构完全一样而设计的,用的是同一个sessionfactory。
2、如果是针对用户使用不同数据库且不同的表结构
类似的可以根据上面的设计搞定不同的sessionfactory,也可以写一个MultiSessionFactory的类来实现(提示:如果多个数据库是不同时使用才需要这样做,如果多个数据库是同时使用,就不必写了,直接在配置文件中几个数据库就配置几个sessionfactory就行了)。
3、还有一种情况是不同数据库有些表结构一样有些不一样:如果一样的就用同个sessionfactory,不同个dataSource,不一样的就在页面传入sessionfactoryName和dataSourceName,让他们分别对号入座就行了!