Heim  >  Artikel  >  Java  >  Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework

Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework

高洛峰
高洛峰Original
2017-02-08 10:44:251742Durchsuche

Wir sind in unserem Projekt auf ein solches Problem gestoßen: Unser Projekt muss eine Verbindung zu mehreren Datenbanken herstellen, und verschiedene Kunden greifen bei jedem Besuch je nach Bedarf auf unterschiedliche Datenbanken zu. In der Vergangenheit haben wir in den Frameworks Spring und Hibernate immer eine Datenquelle konfiguriert, sodass das dataSource-Attribut von sessionFactory immer auf diese Datenquelle verwies und bei Verwendung von sessionFactory unverändert blieb. Aufgrund der Anforderungen des Projekts muss unser DAO beim Zugriff auf die SessionFactory jedoch ständig zwischen mehreren Datenquellen wechseln. Es stellt sich die Frage, wie die SessionFactory bei der Durchführung der Datenpersistenz dynamisch umgeschaltet werden kann Datenquellen? Können wir es mit ein paar Modifikationen unter dem Spring Framework lösen? Gibt es Designmuster, die verwendet werden können?

Analyse des Problems

Ich dachte zuerst daran, alle Datenquellen im applicationContext von Spring zu konfigurieren. Diese Datenquellen können unterschiedlicher Art sein, z. B. unterschiedliche Datenbanken: Oracle, SQL Server, MySQL usw., oder es kann sich um unterschiedliche Datenquellen handeln: z. B. org.apache.commons.dbcp.BasicDataSource, bereitgestellt von Apache, org, bereitgestellt von Spring . springframework.jndi.JndiObjectFactoryBean usw. Anschließend legt sessionFactory das dataSource-Attribut je nach Kundenanforderung auf eine andere Datenquelle fest, um den Zweck des Datenquellenwechsels zu erreichen.

Allerdings habe ich schnell ein Problem entdeckt: Wenn mehrere Benutzer gleichzeitig auf die Datenbank zugreifen, kommt es zu Ressourcenkonflikten. Dies alles ist auf das „Einzelfallmuster“ zurückzuführen. Wie wir alle wissen, übernehmen die in der beanFactory registrierten Beans grundsätzlich den Singleton-Modus, dh wenn der Frühling beginnt, werden diese Beans in den Speicher geladen und jede Bean existiert nur im gesamten Projekt Objekt. Da es nur ein Objekt gibt, verhalten sich alle Eigenschaften des Objekts, genauer gesagt Instanzvariablen, wie statische Variablen (eigentlich sind „statisch“ und „Einzelfall“ oft sehr ähnliche Dinge, wir verwenden oft „statisch“, um „Einzelfall“ zu implementieren "). Nehmen wir unser Problem als Beispiel: sessionFactory hat im gesamten Projekt nur ein Objekt und seine Instanzvariable dataSource hat nur eine Instanzvariable, genau wie eine statische Variable. Wenn verschiedene Benutzer den Wert von dataSource ständig ändern, besteht zwangsläufig das Problem, dass mehrere Benutzer um eine Variable konkurrieren, was zu versteckten Gefahren für das System führt.

Durch die obige Analyse liegt der Schlüssel zur Lösung des Problems des Zugriffs auf mehrere Datenquellen in der Fähigkeit von sessionFactory, durch einen bestimmten Codeabschnitt dynamisch entsprechend den Kundenanforderungen zu wechseln Durchführen von Datenpersistenzdatenquellen und Lösen von Ressourcenkonfliktproblemen.

Lösung des Problems

Verwendung des Decorator-Entwurfsmusters

Um dieses Problem zu lösen, waren meine Gedanken auf diese Datenquelle fixiert. Wenn die dataSource, auf die sessionFactory verweist, entsprechend den Anforderungen des Kunden eine Verbindung zu der vom Kunden benötigten realen Datenquelle herstellen kann, dh die Funktion zum dynamischen Wechseln der Datenquellen bereitstellen kann, ist das Problem gelöst. Was machen wir also? Sollten wir den dataSource-Quellcode ändern, den wir verwenden möchten? Dies ist offensichtlich keine gute Lösung. Wir möchten, dass unsere Änderungen vom ursprünglichen DataSource-Code getrennt werden. Basierend auf der obigen Analyse sollte die Verwendung des Decorator-Musters (Decorator-Muster) im GoF-Entwurfsmuster die beste Option sein, die wir wählen können.

Was ist der „Decorator-Modus“? Einfach ausgedrückt: Wenn wir die ursprüngliche Funktion ändern müssen, den Originalcode aber nicht direkt ändern möchten, entwerfen wir einen Dekorator, der den Originalcode abdeckt. Wenn wir Decorator verwenden, ist es genau das gleiche wie die ursprüngliche Klasse, aber einige Funktionen von Decorator wurden an die Funktionen angepasst, die wir ändern müssen. Die Struktur des Decorator-Musters ist wie in der Abbildung dargestellt.

Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework

Ursprünglich mussten wir einige Funktionen aller spezifischen Komponentenklassen in der Abbildung ändern, aber anstatt ihre Codes direkt zu ändern, haben wir außerhalb von ihnen einen Decorator hinzugefügt. Decorator und die spezifische Component-Klasse erben beide AbstractComponent, sodass sie genauso aussehen wie die spezifische Component-Klasse. Das heißt, wenn wir Decorator verwenden, ist es genau wie bei der Verwendung von ConcreteComponentA oder ConcreteComponentB, selbst bei den Client-Programmen, die ConcreteComponentA oder ConcreteComponentB verwenden Ich weiß nicht, dass die von ihnen verwendete Klasse in Decorator geändert wurde, aber Decorator hat einige Methoden der spezifischen Component-Klasse geändert, und die Ergebnisse der Ausführung dieser Methoden sind unterschiedlich.

Entwerfen Sie die MultiDataSource-Klasse

Nun zurück zu unserem Problem: Wir müssen Änderungen an den Funktionen von dataSource vornehmen, möchten aber keinen Code in dataSource ändern. Die dataSource, auf die ich mich hier beziehe, sind alle Klassen, die die javax.sql.DataSource-Schnittstelle implementieren. Zu den häufig verwendeten gehören org.apache.commons.dbcp.BasicDataSource von Apache, org.springframework.jndi.JndiObjectFactoryBean von Spring usw. Wir verwenden diese Klassen nicht, um sie selbst zu ändern, geschweige denn, sie einzeln zu ändern, um die Funktion der dynamischen Zuweisung von Datenquellen zu realisieren. Gleichzeitig hoffen wir, dass die sessionFactory, die dataSource verwendet, solche Änderungen nicht spürt alle. Das Decorator-Muster ist das Designmuster, das dieses Problem löst.

Schreiben Sie zunächst eine Decorator-Klasse, die ich MultiDataSource genannt habe, und verwenden Sie sie, um Datenquellen dynamisch zu wechseln. Ändern Sie gleichzeitig das dataSource-Attribut von sessionFactory in der Konfigurationsdatei von einer bestimmten dataSource in MultiDataSource. Wie im Bild gezeigt:

Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework

Im Vergleich zum ursprünglichen Decorator-Modus ist AbstractComponent eine abstrakte Klasse, aber hier können wir diese abstrakte Klasse durch eine Schnittstelle ersetzen, nämlich die Die DataSource-Schnittstelle und ConcreteComponent sind die DataSource-Implementierungsklassen wie BasicDataSource, JndiObjectFactoryBean usw. MultiDataSource kapselt eine bestimmte Datenquelle und implementiert den dynamischen Wechsel von Datenquellen:

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

Wenn der Kunde eine Anfrage stellt, fügen Sie den dataSourceName in die Anfrage ein und fügen Sie ihn dann in die ein request Durch Aufrufen von newMultiDataSource(dataSource) können Sie MultiDataSource die Datenquelle mitteilen, die der Kunde benötigt, und Sie können Datenquellen dynamisch wechseln. Aber aufmerksame Freunde werden feststellen, dass dies im Fall eines Singletons ein Problem darstellt, da MultiDataSource nur ein Objekt im System hat und seine Instanzvariable dataSource ebenfalls nur eines hat, genau wie eine statische Variable. Aus diesem Grund hat das Singleton-Muster viele Entwurfsmuster zur Änderung gezwungen, was in meinem Artikel „Singleton ändert unsere Entwurfsmuster“ ausführlich besprochen wird. Wie entwerfen wir also im Singleton-Modus?

MultiDataSource im Singleton-Modus

Da im Singleton-Modus die Datenquelle bei jedem Aufruf der MultiDataSource-Methode unterschiedlich sein kann, können wir dataSource nicht in die Instanzvariable dataSource einfügen . Der einfachste Weg besteht darin, Parameter in der Methode getDataSource() hinzuzufügen, um MultiDataSource mitzuteilen, welche Datenquelle ich aufrufe:

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

Es ist erwähnenswert, dass die Datenquellen ich benötige wurden in der Spring-Konfigurationsdatei registriert und dataSourceName ist die entsprechende 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>

Um den ApplicationContext von Spring zu erhalten, muss die MultiDataSource-Klasse die Schnittstelle org.springframework.context.ApplicationContextAware und die Implementierungsmethode implementieren:

Java-Code

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

Auf diese Weise kann ich die Datenquelle über this.applicationContext.getBean(dataSourceName) abrufen.

dataSourceName über Threads übergeben

Mit Blick auf das obige Design kann MultiDataSource immer noch nicht ausgeführt werden, denn wenn der Benutzer eine Anfrage stellt, zu welcher Datenbank muss er eine Verbindung herstellen? Der Datenquellenname lautet: Um den Datenquellennamen in der Anforderung an MultiDataSource zu übergeben, müssen Sie BUS und DAO durchlaufen. Das heißt, um den Datenquellennamen an MultiDataSource zu übergeben, müssen alle Methoden von BUS und verwendet werden DAO muss den Parameter dataSourceName hinzufügen. Dies ist, was wir nicht sehen möchten. Es ist ein gutes Design, eine Klasse zu schreiben, die BUS und DAO überspringt und sie über Threads direkt an MultiDataSource übergibt:

Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework

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

A Filter, der SpObserver.petSp(dataSourceName) jedes Mal aufruft, wenn der Client eine Anfrage stellt, und den dataSourceName in der Anfrage an das SpObserver-Objekt übergibt. Ändern Sie abschließend die MultiDataSource-Methode getDataSource():

Java-Code

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

Der vollständige MultiDataSource-Code befindet sich im Anhang.

Datenquelle dynamisch hinzufügen

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

更多Konfigurationsbeispielcode zum Erstellen, Laden und dynamischen Wechseln mehrerer Datenquellen im Spring Framework相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn