如何在spring框架中解决多数据源的问题

如何在spring框架中解决多数据源的问题,第1张

在 我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往 在 spring 和 hibernate 框架中总是配置一个数据源,因而 sessionFactory 的dataSource 属性总是指向这个 数据源并且恒定不变,所有 DAO 在使用 sessionFactory 的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们 的 DAO 在访问 sessionFactory 的时候都不得不在多个数据源中不断切换,问题就出现了:如何让 sessionFactory 在执 行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在 spring 的框架下通过少量修改得到解决?是否有什么设计模式可以利用 呢?

问题的分析

我 首先想到在 spring 的 applicationContext 中配置所有的 dataSource 。这些 dataSource 可能是各种 不同类型的,比如不同的数据库: Oracle 、 SQL Server 、 MySQL 等,也可能是不同的数据源:比如 apache 提供的 orgapachecommonsdbcpBasicDataSource 、 spring 提供的 orgspringframeworkjndiJndiObjectFactoryBean 等。然后 sessionFactory 根据客户的 每次请求,将dataSource 属性设置成不同的数据源,以到达切换数据源的目的。

但 是,我很快发现一个问题:当多用户同时并发访问数据库的时候会出现资源争用的问题。这都是“单例模式”惹的祸。众所周知,我们在使用 spring 框架 的时候,在 beanFactory 中注册的 bean 基本上都是采用单例模式,即 spring 在启动的时候,这些 bean 就装载到内存中, 并且每个 bean 在整个项目中只存在一个对象。正因为只存在一个对象,对象的所有属性,更准确说是实例变量,表现得就如同是个静态变量(实际上“静 态”与“单例”往往是非常相似的两个东西,我们常常用“静态”来实现“单例”)。拿我们的问题来说, sessionFactory 在整个项目中只有一 个对象,它的实例变量 dataSource 也就只有一个,就如同一个静态变量一般。如果不同的用户都不断地去修改 dataSource 的值,必然 会出现多用户争用一个变量的问题,对系统产生隐患。

通过以上的分析,解决多数据源访问问题的关键,就集中在 sessionFactory 在执行数据持久化的时候,能够通过某段代码去根据客户的需要动态切换数据源,并解决资源争用的问题。

问题的解决

(一) 采用 Decorator 设计模式

要 解决这个问题,我的思路锁定在了这个 dataSource 上了。如果 sessionFactory 指向的 dataSource 可以根据客户的 需求去连接客户所需要的真正的数据源,即提供动态切换数据源的功能,那么问题就解决了。那么我们怎么做呢?去修改那些我们要使用 的 dataSource 源码吗?这显然不是一个好的方案,我们希望我们的修改与原dataSource 代码是分离的。根据以上的分析,使 用 GoF 设计模式中的 Decorator 模式(装饰者模式)应当是我们可以选择的最佳方案。

什 么是“ Decorator 模式”?简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个 Decorator 套 在原有代码外面。当我们使用 Decorator 的时候与原类完全一样,当 Decorator 的某些功能却已经修改为了我们需要修改的功 能。 Decorator 模式的结构如图。

我 们本来需要修改图中所有具体的 Component 类的一些功能,但却并不是去直接修改它们的代码,而是在它们的外面增加一 个 Decorator 。 Decorator 与具体的 Component 类都是继承的 AbstractComponent ,因此它长得和具 体的 Component 类一样,也就是说我们在使用 Decorator 的时候就如同在使用 ConcreteComponentA 或者 ConcreteComponentB 一样,甚至那些使用 ConcreteComponentA 或者 ConcreteComponentB 的客 户程序都不知道它们用的类已经改为了 Decorator ,但是 Decorator 已经对具体的 Component 类的部分方法进行了修改,执 行这些方法的结果已经不同了。

(二) 设计 MultiDataSource 类

现 在回到我们的问题,我们需要对 dataSource 的功能进行变更,但又不希望修改 dataSource 中的任何代码。我这里指 的 dataSource 是所有实现 javaxsqlDataSource 接口的类,我们常用的包括 apache 提供的 orgapachecommonsdbcpBasicDataSource 、 spring 提供的 orgspringframeworkjndiJndiObjectFactoryBean 等,这些类我们不可能修改它们本身,更不可能对它们一 个个地修改以实现动态分配数据源的功能,同时,我们又希望使用 dataSource 的 sessionFactory 根本就感觉不到这样的变 化。 Decorator 模式就正是解决这个问题的设计模式。

首先写一个 Decorator 类,我取名叫 MultiDataSource,通过它来动态切换数据源 。同时在配置文件中将sessionFactory的dataSource属性由原来的某个具体的dataSource改为MultiDataSource。如图:

对 比原Decorator 模式, AbstractComponent 是一个抽象类,但在这里我们可以将这个抽象类用接口来代替,即 DataSource 接口,而 ConcreteComponent 就是那些 DataSource 的实现类, 如 BasicDataSource 、JndiObjectFactoryBean 等。 MultiDataSource 封装了具体的 dataSource,并实现了数据源动态切换:

java 代码

public class MultiDataSource implements DataSource {

private DataSource dataSource = null ;

public MultiDataSource(DataSource dataSource){

this dataSource = dataSource;

}

public Connection getConnection() throws SQLException {

return getDataSource()getConnection();

}

//其它DataSource接口应当实现的方法

public DataSource getDataSource(){

return this dataSource;

}

}

public void setDataSource(DataSource dataSource) {

this dataSource = dataSource;

}

}

客 户在发出请求的时候,将dataSourceName放到request中,然后把request中的数据源名通过调用 newMultiDataSource(dataSource) 时可以告诉 MultiDataSource 客户需要的数据源,就可以实现动态切换数 据源了。但细心的朋友会发现这在单例的情况下就是问题的,因为 MultiDataSource 在系统中只有一个对象,它的实例变 量 dataSource 也只有一个,就如同一个静态变量一般。正因为如此, 单例模式让许多设计模式都不得不需要更改,这将在我的《“单例”更改了我 们的设计模式》中详细讨论。那么,我们在单例模式下如何设计呢?

(三) 单例模式下的 MultiDataSource

在 单例模式下,由于我们在每次调用 MultiDataSource 的方法的时候, dataSource 都可能是不同的,所以我们不能 将 dataSource 放在实例变量 dataSource 中,最简单的方式就是在方法 getDataSource() 中增加参数,告 诉 MultiDataSource 我到底调用的是哪个 dataSource :

java 代码

public DataSource getDataSource(String dataSourceName){

logdebug( "dataSourceName:" +dataSourceName);

try {

if (dataSourceName== null ||dataSourceNameequals( "" )){

return this dataSource;

}

return (DataSource) this applicationContextgetBean(dataSourceName);

} catch (NoSuchBeanDefinitionException ex){

throw new DaoException( "There is not the dataSource

}

}

值得一提的是,我需要的数据源已经都在spring 的配置文件中注册, dataSourceName 就是其对应的 id 。

xml 代码

< bean id = "dataSource1"

class = "orgapachecommonsdbcpBasicDataSource" >

< property name = "driverClassName" >

< value > oraclejdbcdriverOracleDriver value >

property >

bean >

< bean id = "dataSource2"

class = "orgapachecommonsdbcpBasicDataSource" >

< property name = "driverClassName" >

< value > oraclejdbcdriverOracleDriver value >

property >

bean >

为了得到spring 的 ApplicationContext , MultiDataSource 类必须实现接口orgspringframeworkcontextApplicationContextAware ,并且实现方法:

java 代码

private ApplicationContext applicationContext = null ;

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

this applicationContext = applicationContext;

}

如此这样,我就可以通过this applicationContext getBean(dataSourceName) 得到 dataSource 了。

(四) 通过线程传递 dataSourceName

查 看以上设计, MultiDataSource 依然无法运行,因为用户在发出请求时,他需要连接什么数据库,其数据源名是放在 request 中的, 要将 request 中的数据源名传给 MultiDataSource ,需要经过 BUS 和 DAO ,也就是说为了把数据源名传 给 MultiDataSource , BUS 和 DAO 的所有方法都要增加 dataSourceName 的参数,这是我们不愿看到的。写一个 类,通过线程的方式跳过 BUS 和 DAO 直接传递给 MultiDataSource 是一个不错的设计:

java 代码

public class SpObserver {

private static ThreadLocal local = new ThreadLocal();

public static void putSp(String sp) {

localset(sp);

}

public static String getSp() {

return (String)localget();

}

}

做一个filter ,每次客户发出请求的时候就调用 SpObserverpetSp ( dataSourceName ) ,将 request 中的dataSourceName 传递给 SpObserver 对象。 最后修改 MultiDataSource 的方法 getDataSource() :

java 代码

public DataSource getDataSource(){

String sp = SpObservergetSp();

return getDataSource(sp);

}

完整的MultiDataSource 代码在附件中。

(五) 动态添加数据源

通 过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在 spring 的 ApplicationContext 中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇 到。 spring 的 ApplicationContext 是在项目启动的时候加载的。加载以后,我们如何动态地加载新的 bean 到 ApplicationContext 中呢?我想到如果用 spring 自己的方法解决这个问题就好了。所幸的是,在查看 spring 的源代码后,我找到了这样的代码,编写了 DynamicLoadBean 类,只要 调用loadBean() 方法,就可以将某个或某 几个配置文件中的 bean 加载到 ApplicationContext 中(见附件)。不通过配置文件直接加载对象,在spring 的源码中也 有,感兴趣的朋友可以自己研究。

(六) 在 spring 中配置

在完成了所有这些设计以后,我最后再唠叨一句。我们应当在 spring 中做如下配置:

xml 代码

< bean id = "dynamicLoadBean" class = "comhtxxservicedaoDynamicLoadBean" > bean >

< bean id = "dataSource" class = "comhtxxservicedaoMultiDataSource" >

< property name = "dataSource" >

< ref bean = "dataSource1" />

property >

bean >

< bean id = "sessionFactory" class = "orgspringframeworkormhibernate3LocalSessionFactoryBean" >

< property name = "dataSource" >

< ref bean = "dataSource" />

property >

bean >

其中dataSource 属性实际上更准确地说应当是 defaultDataSource ,即 spring 启动时以及在客户没有指定数据源时应当指定的默认数据源。

该方案的优势

以上方案与其它方案相比,它有哪些优势呢?

首 先,这个方案完全是在 spring 的框架下解决的,数据源依然配置在 spring 的配置文件中, sessionFactory依然去配置它 的 dataSource 属性,它甚至都不知道 dataSource 的改变。唯一不同的是在真正的 dataSource 与 sessionFactory 之间增加了一个 MultiDataSource 。

其 次,实现简单,易于维护。这个方案虽然我说了这么多东西,其实都是分析,真正需要我们写的代码就只有 MultiDataSource 、 SpObserver 两个类。 MultiDataSource 类真正要写的只 有 getDataSource() 和getDataSource(sp) 两个方法,而 SpObserver 类更简单了。实现越简单,出错的可能 就越小,维护性就越高。

最 后,这个方案可以使单数据源与多数据源兼容。这个方案完全不影响 BUS 和 DAO 的编写。如果我们的项目在开始之初是单数据源的情况下开发,随着项 目的进行,需要变更为多数据源,则只需要修改 spring 配置,并少量修改 MVC 层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项 目希望改回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的d性。

特 别说明:实例中的DynamicLoadBean在web环境下运行会出错,需要将类中AbstractApplicationContext改为 orgspringframeworkcontextConfigurableApplicationContext。

第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一个整数加法求和:

LIBEXPORT_API int mySum(int a,int b){ return a+b;}

C# 导入定义:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAuto,CallingConvention=CallingConventionStdCall)]

public static extern int mySum (int a,int b);

}

在C#中调用测试:

int iSum = RefCommmySum(,);

运行查看结果iSum为5,调用正确。第一步试验完成,说明在C#中能够调用自定义的动态链接库函数。

第二步,我定义了字符串 *** 作的函数(简单起见,还是采用前面的函数名),返回结果为字符串:

LIBEXPORT_API char mySum(char a,char b){sprintf(b,"%s",a); return a;}

C# 导入定义:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAuto,

CallingConvention=CallingConventionStdCall)]

public static extern string mySum (string a, string b);

}

在C#中调用测试:

string strDest="";

string strTmp= RefCommmySum("45", strDest);

运行查看结果 strTmp 为"45",但是strDest为空。我修改动态链接库实现,返回结果为串b:

LIBEXPORT_API char mySum(char a,char b){sprintf(b,"%s",a) return b;}

修改 C# 导入定义,将串b修改为ref方式:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAuto,CallingConvention=CallingConventionStdCall)]

public static extern string mySum (string a, ref string b);

}

在C#中再调用测试:

string strDest="";

string strTmp= RefCommmySum("45", ref strDest);

运行查看结果 strTmp 和 strDest 均不对,含不可见字符。再修改 C# 导入定义,将CharSet从Auto修改为Ansi:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAnsi,CallingConvention=CallingConventionStdCall)]

public static extern string mySum (string a, string b);

}

在C#中再调用测试:

string strDest="";

string strTmp= RefComm mySum("45", ref strDest);

运行查看结果 strTmp 为"45",但是串 strDest 没有赋值。第二步实现函数返回串,但是在函数出口参数中没能进行输出。再次修改 C# 导入定义,将串b修改为引用(ref):

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAnsi,CallingConvention=CallingConventionStdCall)]

public static extern string mySum (string a, ref string b);

}

运行时调用失败,不能继续执行。

第三步,修改动态链接库实现,将b修改为双重指针:

LIBEXPORT_API char mySum(char a,char b){sprintf((b),"%s",a); return b;}

C#导入定义:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAnsi,CallingConvention=CallingConventionStdCall)]

public static extern string mySum (string a, ref string b);

}

在C#中调用测试:

string strDest="";

string strTmp= RefComm mySum("45", ref strDest);

运行查看结果 strTmp 和 strDest 均为"45",调用正确。第三步实现了函数出口参数正确输出结果。

第四步,修改动态链接库实现,实现整数参数的输出:

LIBEXPORT_API int mySum(int a,int b,int c){ c=a+b; return c;}

C#导入的定义:

public class RefComm

{

[DllImport("LibEncryptdll",

EntryPoint=" mySum ",

CharSet=CharSetAnsi,CallingConvention=CallingConventionStdCall)]

public static extern int mySum (int a, int b,ref int c);

}

在C#中调用测试:

int c=0;

int iSum= RefComm mySum(,, ref c);

运行查看结果iSum 和c均为5,调用正确。

经过以上几个步骤的试验,基本掌握了如何定义动态库函数以及如何在 C# 定义导入,有此基础,很快我实现了变长加密函数在 C# 中的调用,至此目标实现。

三、结论

在 C# 中调用 C++ 编写的动态链接库函数,如果需要出口参数输出,则需要使用指针,对于字符串,则需要使用双重指针,对于 C# 的导入定义,则需要使用引用(ref)定义。

对于函数返回值,C# 导入定义和 C++ 动态库函数声明定义需要保持一致,否则会出现函数调用失败。定义导入时,一定注意 CharSet 和 CallingConvention 参数,否则导致调用失败或结果异常。运行时,动态链接库放在 C# 程序的目录下即可,我这里是一个 C# 的动态链接库,两个动态链接库就在同一个目录下运行。

这个在spring的 applicationComtextxml文件里配置(一般是这个文件 你也可以把他改为其他文件名),

#oracle\u89c6\u56fe\u6570\u636e\u5e93\u8fde\u63a5\u53c2\u6570

hibernatedialect=orghibernatedialectOracleDialect

validationQueryoracle=SELECT 1 FROM DUAL

jdbcurlcrm=jdbc:oracle:thin:@localhost:1521:orcl

jdbcusernamecrm=crm

jdbcpasswordcrm=crm

jdbcdbType=oracle

#mysql \u6570\u636e\u5e93\u8fde\u63a5\u53c2\u6570

#hibernatedialect=orghibernatedialectMySQLDialect

#validationQuerysqlserver=SELECT 1

#jdbcurlcrm=jdbc:mysql://localhost:3306/testuseUnicode=true&characterEncoding=UTF-8

#jdbcusernamecrm=root

#jdbcpasswordcrm=root

#jdbcdbType=mysql 

//配置数据源  

 <bean id="dbcp_dataSource" destroy-method="close" class="orgapachecommonsdbcpBasicDataSource">

  <property name="driverClassName" value="${jdbcdriverClassName}" />

  <property name="url" value="${jdbcurlcrm}" />

  <property name="username" value="${jdbcusernamecrm}" />

  <property name="password" value="${jdbcpasswordcrm}" />

 </bean> --> 

在这里你可以多配置几个数据源  

。。。。

然后就可以动态的修改数据源集合了

<bean id="sessionFactory"

  class="orgspringframeworkormhibernate3LocalSessionFactoryBean">

  <property name="dataSource" ref="dbcp_dataSource" />

  <!--ORM映射文件:mappingResources-->

  <!-- ORM目录 -->

  <property name="mappingDirectoryLocations">

   <value>classpath:com/zakhas/ospm/model/hbm/</value>

  </property>

这里的dbcp_dataSource   是我的oracle数据库,你可以在这里切换其他的数据库。

因为涉及到一些项目,我做了些改动,可能有些错误,不知道你看懂了啵

以上就是关于如何在spring框架中解决多数据源的问题全部的内容,包括:如何在spring框架中解决多数据源的问题、切换数据库动态调用的时候,直接调用的模型类DB 方法,这个DB 方法怎么在模型类中定义dbname属性、spring 怎么动态配置 连接不同的数据库。oracle 或是mysql等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9756896.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存