We encountered such a problem in our project: Our project needs to connect to multiple databases, and different customers will access different databases according to their needs during each visit. In the past, we always configured a data source in the spring and hibernate frameworks, so the dataSource attribute of sessionFactory always pointed to this data source and remained unchanged. All DAOs access the database through this data source when using sessionFactory. But now, due to the needs of the project, our DAO has to constantly switch between multiple data sources when accessing the sessionFactory. The question arises: how to make the sessionFactory dynamically switch according to the needs of the customer when performing data persistence. Different data sources? Can we solve it with a few modifications under the spring framework? Are there any design patterns that can be utilized?
Analysis of the problem
I first thought of configuring all dataSources in spring's applicationContext. These dataSources may be of various types, such as different databases: Oracle, SQL Server, MySQL, etc., or they may be different data sources: such as org.apache.commons.dbcp.BasicDataSource provided by apache, org provided by spring. springframework.jndi.JndiObjectFactoryBean etc. Then sessionFactory sets the dataSource attribute to a different data source according to each customer request to achieve the purpose of switching data sources.
However, I quickly discovered a problem: when multiple users concurrently access the database at the same time, resource contention will occur. This is all caused by the "single case pattern". As we all know, when we use the spring framework, the beans registered in the beanFactory basically adopt the singleton mode, that is, when spring starts, these beans are loaded into the memory, and each bean only exists in the entire project. an object. Because there is only one object, all the properties of the object, more precisely instance variables, behave like static variables (actually "static" and "single case" are often very similar things, we often use " static" to implement "singleton"). Taking our problem as an example, sessionFactory has only one object in the entire project, and its instance variable dataSource has only one instance variable, just like a static variable. If different users constantly modify the value of dataSource, there will inevitably be a problem of multiple users competing for a variable, which will cause hidden dangers to the system.
Through the above analysis, the key to solving the problem of multi-data source access is focused on the ability of sessionFactory to dynamically switch according to customer needs through a certain piece of code when performing data persistence. data sources and resolve resource contention issues.
Solution to the problem
Using the Decorator design pattern
To solve this problem, my idea is locked on this dataSource. If the dataSource pointed to by sessionFactory can connect to the real data source needed by the customer according to the customer's needs, that is, provide the function of dynamically switching data sources, then the problem will be solved. So what do we do? Should we modify the dataSource source code we want to use? This is obviously not a good solution, we want our modifications to be separated from the original dataSource code. Based on the above analysis, using the Decorator pattern (decorator pattern) in the GoF design pattern should be the best option we can choose.
What is "Decorator mode"? To put it simply, when we need to modify the original function, but we do not want to modify the original code directly, we design a Decorator to cover the original code. When we use Decorator, it is exactly the same as the original class, but some functions of Decorator have been modified to the functions we need to modify. The structure of the Decorator pattern is as shown in the figure.
We originally needed to modify some functions of all specific Component classes in the figure, but instead of modifying their codes directly, we added a Decorator outside them. Decorator and the specific Component class both inherit AbstractComponent, so it looks the same as the specific Component class. That is to say, when we use Decorator, it is just like using ConcreteComponentA or ConcreteComponentB, even those client programs that use ConcreteComponentA or ConcreteComponentB They don't know that the class they use has been changed to Decorator, but Decorator has modified some methods of the specific Component class, and the results of executing these methods are different.
Design MultiDataSource class
Now back to our problem, we need to make changes to the functions of dataSource, but we do not want to modify any code in dataSource. The dataSource I am referring to here are all classes that implement the javax.sql.DataSource interface. Commonly used ones include org.apache.commons.dbcp.BasicDataSource provided by apache, org.springframework.jndi.JndiObjectFactoryBean provided by spring, etc. We do not use these classes. It is possible to modify them themselves, let alone modify them one by one to realize the function of dynamically allocating data sources. At the same time, we hope that the sessionFactory using dataSource will not feel such changes at all. The Decorator pattern is the design pattern that solves this problem.
First write a Decorator class, which I named MultiDataSource, and use it to dynamically switch data sources. At the same time, change the dataSource attribute of sessionFactory from a specific dataSource to MultiDataSource in the configuration file. As shown in the figure:
Compared with the original Decorator mode, AbstractComponent is an abstract class, but here we can replace this abstract class with an interface, that is, the DataSource interface, and ConcreteComponent is Those DataSource implementation classes, such as BasicDataSource, JndiObjectFactoryBean, etc. MultiDataSource encapsulates a specific dataSource and implements dynamic switching of data sources:
Java Code
public class MultiDataSource implements DataSource { private DataSource dataSource = null; public MultiDataSource(DataSource dataSource){ this.dataSource = dataSource; } /* (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws SQLException { return getDataSource().getConnection(); } //其它DataSource接口应当实现的方法 public DataSource getDataSource(){ return this.dataSource; } } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
When the customer makes a request, the dataSourceName is placed in the request, and then the data in the request is The source name can tell MultiDataSource the data source that the customer needs when calling newMultiDataSource(dataSource), so that the data source can be dynamically switched. But careful friends will find that this is a problem in the case of a singleton, because MultiDataSource has only one object in the system, and its instance variable dataSource also has only one, just like a static variable. Because of this, the singleton pattern has forced many design patterns to change, which will be discussed in detail in my article "Singleton Changes Our Design Patterns". So, how do we design in singleton mode?
MultiDataSource in singleton mode
In singleton mode, since the dataSource may be different every time we call the MultiDataSource method, so We cannot put dataSource in the instance variable dataSource. The simplest way is to add parameters to the method getDataSource() to tell MultiDataSource which dataSource I am calling:
java code
public DataSource getDataSource(String dataSourceName){ log.debug("dataSourceName:"+dataSourceName); try{ if(dataSourceName==null||dataSourceName.equals("")){ return this.dataSource; } return (DataSource)this.applicationContext.getBean(dataSourceName); }catch(NoSuchBeanDefinitionException ex){ throw new DaoException("There is not the dataSource } }
It is worth mentioning that the data sources I need have been registered in the spring configuration file, and dataSourceName is its corresponding ID.
xml Code
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDrivervalue> property> ...... bean> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDrivervalue> property> ...... bean>
In order to get spring's ApplicationContext, the MultiDataSource class must implement the interface org.springframework.context.ApplicationContextAware, and implement the method:
java Code
private ApplicationContext applicationContext = null; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
In this way, I can get the dataSource through this.applicationContext.getBean(dataSourceName).
Passing dataSourceName through threads
Looking at the above design, MultiDataSource still cannot run because when the user makes a request, what database does he need to connect to? The data source name is In the request, to pass the data source name in the request to MultiDataSource, you need to go through BUS and DAO. That is to say, in order to pass the data source name to MultiDataSource, all methods of BUS and DAO must add the parameter dataSourceName. This is What we don’t want to see. It is a good design to write a class that skips BUS and DAO and passes it directly to MultiDataSource through threads:
java code
public class SpObserver { private static ThreadLocal local = new ThreadLocal(); public static void putSp(String sp) { local.set(sp); } public static String getSp() { return (String)local.get(); } }
Make a filter , every time the client makes a request, SpObserver.petSp(dataSourceName) is called and the dataSourceName in the request is passed to the SpObserver object. Finally modify the MultiDataSource method getDataSource():
java code
public DataSource getDataSource(){ String sp = SpObserver.getSp(); return getDataSource(sp); }
The complete MultiDataSource code is in the attachment.
Dynamicly add data source
通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在spring的ApplicationContext 中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。spring的 ApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的bean到ApplicationContext中 呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了 DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到 ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。
在spring中配置
在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:
xml 代码
<bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean> <bean id="dataSource" class="com.htxx.service.dao.MultiDataSource"> <property name="dataSource"> <ref bean="dataSource1" /> property> bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <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层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项目希望改 回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的弹性。
特别说明:实例中的DynamicLoadBean在web环境下运行会出错,需要将类中 AbstractApplicationContext改为 org.springframework.context.ConfigurableApplicationContext。
更多Configuration example code for creating, loading and dynamically switching multiple data sources in the spring framework相关文章请关注PHP中文网!