Maison  >  Article  >  Java  >  Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring

Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring

高洛峰
高洛峰original
2017-02-08 10:44:251804parcourir

Nous avons rencontré un tel problème dans notre projet : notre projet doit se connecter à plusieurs bases de données, et différents clients accéderont à différentes bases de données en fonction de leurs besoins lors de chaque visite. Dans le passé, nous configurions toujours une source de données dans les frameworks Spring et Hibernate, de sorte que l'attribut dataSource de sessionFactory pointait toujours vers cette source de données et restait inchangé. Tous les DAO accèdent à la base de données via cette source de données lors de l'utilisation de sessionFactory. Mais maintenant, en raison des besoins du projet, notre DAO doit constamment basculer entre plusieurs sources de données lors de l'accès à sessionFactory. La question se pose : comment faire basculer dynamiquement la sessionFactory en fonction des besoins du client lors de la persistance des données. sources de données ? Pouvons-nous le résoudre avec quelques modifications dans le framework Spring ? Existe-t-il des modèles de conception qui peuvent être utilisés ?

Analyse du problème

J'ai d'abord pensé à configurer toutes les sources de données dans l'applicationContext de Spring. Ces dataSources peuvent être de différents types, comme différentes bases de données : Oracle, SQL Server, MySQL, etc., ou il peut s'agir de sources de données différentes : telles que org.apache.commons.dbcp.BasicDataSource fourni par apache, org fourni par spring. .springframework.jndi.JndiObjectFactoryBean etc. Ensuite, sessionFactory définit l'attribut dataSource sur une source de données différente en fonction de chaque demande du client pour atteindre l'objectif de changement de source de données.

Cependant, j'ai rapidement découvert un problème : lorsque plusieurs utilisateurs accèdent simultanément à la base de données en même temps, un conflit de ressources se produit. Tout cela est dû au « modèle de cas unique ». Comme nous le savons tous, lorsque nous utilisons le framework Spring, les beans enregistrés dans beanFactory adoptent essentiellement le mode singleton, c'est-à-dire que lorsque le printemps démarre, ces beans sont chargés dans la mémoire et chaque bean n'existe que dans l'ensemble du projet. objet. Parce qu'il n'y a qu'un seul objet, toutes les propriétés de l'objet, plus précisément les variables d'instance, se comportent comme des variables statiques (en fait, "statique" et "cas unique" sont souvent des choses très similaires, nous utilisons souvent " static " pour implémenter " cas unique " "). En prenant notre problème comme exemple, sessionFactory n'a qu'un seul objet dans l'ensemble du projet et sa variable d'instance dataSource n'a qu'une seule variable d'instance, tout comme une variable statique. Si différents utilisateurs modifient constamment la valeur de dataSource, il y aura inévitablement un problème de plusieurs utilisateurs en compétition pour une variable, ce qui entraînera des dangers cachés pour le système.

Grâce à l'analyse ci-dessus, la clé pour résoudre le problème de l'accès à plusieurs sources de données se concentre sur la capacité de sessionFactory à basculer dynamiquement en fonction des besoins du client via un certain morceau de code lorsque effectuer la persistance des données et résoudre les problèmes de conflit de ressources.

Solution au problème

Utilisation du modèle de conception Decorator

Pour résoudre ce problème, mes pensées se sont verrouillées sur cette source de données. Si la source de données pointée par sessionFactory peut se connecter à la source de données réelle dont le client a besoin en fonction des besoins du client, c'est-à-dire fournir la fonction de commutation dynamique des sources de données, alors le problème sera résolu. Alors qu'est-ce qu'on fait ? Devons-nous modifier le code source dataSource que nous souhaitons utiliser ? Ce n'est évidemment pas une bonne solution, nous souhaitons que nos modifications soient séparées du code dataSource d'origine. Sur la base de l'analyse ci-dessus, l'utilisation du modèle Decorator (modèle décorateur) dans le modèle de conception GoF devrait être la meilleure option que nous puissions choisir.

Qu'est-ce que le « Mode Décorateur » ? Pour faire simple, lorsque nous avons besoin de modifier la fonction d'origine, mais que nous ne voulons pas modifier directement le code d'origine, nous concevons un Decorator pour couvrir le code d'origine. Lorsque nous utilisons Decorator, c'est exactement la même chose que la classe d'origine, mais certaines fonctions de Decorator ont été modifiées pour correspondre aux fonctions que nous devons modifier. La structure du motif Decorator est celle indiquée sur la figure.

Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring

Nous devions à l'origine modifier certaines fonctions de toutes les classes de composants spécifiques dans le diagramme, mais au lieu de modifier directement leurs codes, nous avons ajouté un décorateur en dehors d'elles. Decorator et la classe Component spécifique héritent tous deux de AbstractComponent, ils ressemblent donc à la classe Component spécifique. Autrement dit, lorsque nous utilisons Decorator, c'est comme si nous utilisions ConcreteComponentA ou ConcreteComponentB, même les programmes clients qui utilisent ConcreteComponentA ou ConcreteComponentB. Je ne sais pas que la classe qu'ils utilisent a été remplacée par Decorator, mais Decorator a modifié certaines méthodes de la classe Component spécifique et les résultats de l'exécution de ces méthodes sont différents.

Concevoir la classe MultiDataSource

Revenons maintenant à notre problème, nous devons apporter des modifications aux fonctions de dataSource, mais nous ne voulons modifier aucun code dans dataSource. Les dataSource auxquelles je fais référence ici sont toutes les classes qui implémentent l'interface javax.sql.DataSource. Les classes couramment utilisées incluent org.apache.commons.dbcp.BasicDataSource fourni par Apache, org.springframework.jndi.JndiObjectFactoryBean fourni par Spring, etc. Nous n'utilisons pas ces classes.Il est possible de les modifier elles-mêmes, encore moins de les modifier une par une pour réaliser la fonction d'allocation dynamique des sources de données. En même temps, nous espérons que la sessionFactory utilisant dataSource ne ressentira pas de tels changements. tous. Le modèle Decorator est le modèle de conception qui résout ce problème.

Écrivez d'abord une classe Decorator, que j'ai nommée MultiDataSource, et utilisez-la pour changer dynamiquement de source de données. Dans le même temps, modifiez l'attribut dataSource de sessionFactory d'un dataSource spécifique à MultiDataSource dans le fichier de configuration. Comme le montre l'image :

Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring

Par rapport au mode Décorateur d'origine, AbstractComponent est une classe abstraite, mais ici nous pouvons remplacer cette classe abstraite par une interface, c'est-à-dire le L'interface DataSource et ConcreteComponent correspondent aux classes d'implémentation DataSource, telles que BasicDataSource, JndiObjectFactoryBean, etc. MultiDataSource encapsule une dataSource spécifique et implémente la commutation dynamique des sources de données :

Code Java

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;     
    }     
}

Lorsque le client fait une demande, mettez le dataSourceName dans la demande, puis placez-le dans le request En appelant newMultiDataSource(dataSource), vous pouvez indiquer à MultiDataSource la source de données dont le client a besoin et vous pouvez changer dynamiquement de source de données. Mais les amis prudents découvriront que c'est un problème dans le cas d'un singleton, car MultiDataSource n'a qu'un seul objet dans le système, et sa variable d'instance dataSource n'en a également qu'un, tout comme une variable statique. Pour cette raison, le modèle singleton a forcé de nombreux modèles de conception à changer, ce qui sera discuté en détail dans mon article « Singleton modifie nos modèles de conception ». Alors, comment concevoir en mode singleton ?

MultiDataSource en mode singleton

En mode singleton, puisque la dataSource peut être différente à chaque fois que nous appelons la méthode MultiDataSource, nous ne pouvons donc pas mettre dataSource dans la variable d'instance dataSource . Le moyen le plus simple est d'ajouter des paramètres dans la méthode getDataSource() pour indiquer à MultiDataSource quelle dataSource j'appelle :

code java

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   
        }     
    }

Il convient de mentionner les sources de données dont j'ai besoin. ont été enregistrés dans le fichier de configuration Spring et dataSourceName est son identifiant correspondant.

Code XML

<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>

Afin d'obtenir l'ApplicationContext de Spring, la classe MultiDataSource doit implémenter l'interface org.springframework.context.ApplicationContextAware et la méthode d'implémentation :

code java

private ApplicationContext applicationContext = null;     
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {     
        this.applicationContext = applicationContext;     
    }

De cette façon, je peux obtenir la source de données via this.applicationContext.getBean(dataSourceName).

Transmettre dataSourceName via les threads

En regardant la conception ci-dessus, MultiDataSource ne peut toujours pas s'exécuter car lorsque l'utilisateur fait une demande, à quelle base de données doit-il se connecter ? le nom de la source de données est Dans la requête, pour transmettre le nom de la source de données dans la requête à MultiDataSource, vous devez passer par BUS et DAO C'est-à-dire que pour transmettre le nom de la source de données à MultiDataSource, toutes les méthodes de BUS et. DAO doit ajouter le paramètre dataSourceName. C'est ce que nous ne voulons pas voir. C'est une bonne conception d'écrire une classe qui ignore BUS et DAO et la transmet directement à MultiDataSource via des threads :

Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring

code java

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();     
    }     
}

A filtre qui appelle SpObserver.petSp(dataSourceName) chaque fois qu'un client fait une demande, en transmettant le dataSourceName dans la demande à l'objet SpObserver. Modifiez enfin la méthode MultiDataSource getDataSource() :

code java

public DataSource getDataSource(){     
        String sp = SpObserver.getSp();     
        return getDataSource(sp);     
    }

Le code MultiDataSource complet est en pièce jointe.

Ajouter dynamiquement une source de données

通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在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。

更多Exemple de code de configuration pour créer, charger et commuter dynamiquement plusieurs sources de données dans le framework Spring相关文章请关注PHP中文网!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn